1use alloc::string::String;
40use alloc::vec::Vec;
41use core::cmp::Ordering;
42
43#[path = "i18n_unicode.rs"]
44mod tables;
45use tables::{COMPAT, DECOMP, MARKS, ZEROS};
46
47use super::Locale;
48
49pub const COLL_PRIMARY: u32 = 1;
51pub const COLL_SECONDARY: u32 = 2;
53pub const COLL_TERTIARY: u32 = 3;
55pub const COLL_IDENTICAL: u32 = 4;
57
58const P_SPACE: u32 = 0x0100_0000;
61const P_PUNCT: u32 = 0x0200_0000;
62const P_DIGIT: u32 = 0x0400_0000;
63const P_NUMBER: u32 = P_DIGIT + 0x100; const P_LATIN: u32 = 0x0800_0000;
65const P_OTHER: u32 = 0x1000_0000;
66
67const S_COMMON: u32 = 1;
69const S_STROKE: u32 = 0x40;
70const S_LIG: u32 = 0x41;
71const MARK_ORDER: [char; 16] = [
73 '\u{301}', '\u{300}', '\u{306}', '\u{302}', '\u{30C}', '\u{30A}', '\u{308}', '\u{30B}', '\u{303}', '\u{307}', '\u{327}', '\u{328}',
74 '\u{304}', '\u{323}', '\u{331}', '\u{313}',
75];
76const DIAER: u32 = mark_weight('\u{308}');
77
78const fn mark_weight(m: char) -> u32 {
79 let mut k = 0;
80 while k < MARK_ORDER.len() {
81 if MARK_ORDER[k] == m {
82 return 2 + k as u32;
83 }
84 k += 1;
85 }
86 0x100 + m as u32
87}
88
89#[derive(Clone, Copy)]
93struct Tail {
94 base: char,
95 step: i8,
96 sec: u32,
97 var: bool,
98 then: char,
99}
100
101const fn at(base: char, step: i8) -> Tail {
103 Tail { base, step, sec: 0, var: false, then: '\0' }
104}
105const fn like(base: char, sec: u32) -> Tail {
107 Tail { base, step: 0, sec, var: false, then: '\0' }
108}
109const fn var(base: char) -> Tail {
111 Tail { base, step: 0, sec: 0, var: true, then: '\0' }
112}
113const fn exp(a: char, sec: u32, b: char, var: bool) -> Tail {
115 Tail { base: a, step: 0, sec, var, then: b }
116}
117
118type Rules = &'static [(&'static str, Tail)];
120
121static ROOT: Rules = &[
123 ("ß", exp('s', 0, 's', true)),
124 ("æ", exp('a', 0, 'e', true)),
125 ("œ", exp('o', 0, 'e', true)),
126 ("ð", at('d', 1)),
127 ("ə", at('e', 1)),
128 ("ı", at('i', 1)),
129 ("ŋ", at('n', 1)),
130 ("þ", at('z', 1)),
131 ("ø", like('o', S_STROKE)),
132 ("ł", like('l', S_STROKE)),
133 ("đ", like('d', S_STROKE)),
134 ("ħ", like('h', S_STROKE)),
135 ("ŧ", like('t', S_STROKE)),
136 ("ƀ", like('b', S_STROKE)),
137 ("ɨ", like('i', S_STROKE)),
138 ("ƶ", like('z', S_STROKE)),
139 ("ǥ", like('g', S_STROKE)),
140 ("ґ", at('г', 1)),
142 ("є", at('е', 1)),
143 ("і", at('и', 1)),
144 ("і\u{308}", at('и', 2)),
145 ("и\u{306}", at('й', 0)),
146 ("ђ", at('д', 1)),
147 ("ѕ", at('з', 1)),
148 ("ј", at('й', 1)),
149 ("љ", at('л', 1)),
150 ("њ", at('н', 1)),
151 ("ћ", at('т', 1)),
152 ("у\u{306}", at('у', 1)),
153 ("џ", at('ч', 1)),
154 ("ך", var('כ')),
156 ("ם", var('מ')),
157 ("ן", var('נ')),
158 ("ף", var('פ')),
159 ("ץ", var('צ')),
160];
161
162static DE_PHONEBOOK: Rules = &[("a\u{308}", exp('a', DIAER, 'e', false)), ("o\u{308}", exp('o', DIAER, 'e', false)), ("u\u{308}", exp('u', DIAER, 'e', false))];
163
164static SV: Rules = &[
165 ("a\u{30A}", at('z', 1)),
166 ("a\u{308}", at('z', 2)),
167 ("æ", Tail { sec: S_LIG, ..at('z', 2) }),
168 ("o\u{308}", at('z', 3)),
169 ("ø", Tail { sec: S_STROKE, ..at('z', 3) }),
170 ("u\u{308}", like('y', DIAER)),
171];
172
173static FI: Rules = &[
174 ("a\u{30A}", at('z', 1)),
175 ("a\u{308}", at('z', 2)),
176 ("æ", Tail { sec: S_LIG, ..at('z', 2) }),
177 ("o\u{308}", at('z', 3)),
178 ("ø", Tail { sec: S_STROKE, ..at('z', 3) }),
179 ("o\u{303}", Tail { sec: mark_weight('\u{303}'), ..at('z', 3) }),
180 ("u\u{308}", like('y', DIAER)),
181];
182
183static DA: Rules = &[
184 ("æ", at('z', 1)),
185 ("a\u{308}", Tail { sec: DIAER, ..at('z', 1) }),
186 ("ø", at('z', 2)),
187 ("o\u{308}", Tail { sec: DIAER, ..at('z', 2) }),
188 ("a\u{30A}", at('z', 3)),
189 ("aa", Tail { var: true, ..at('z', 3) }),
190 ("u\u{308}", like('y', DIAER)),
191];
192
193static ES: Rules = &[("n\u{303}", at('n', 1))];
194
195static CS: Rules = &[("c\u{30C}", at('c', 1)), ("ch", at('h', 1)), ("r\u{30C}", at('r', 1)), ("s\u{30C}", at('s', 1)), ("z\u{30C}", at('z', 1))];
196
197static SK: Rules = &[
198 ("a\u{308}", at('a', 1)),
199 ("c\u{30C}", at('c', 1)),
200 ("ch", at('h', 1)),
201 ("o\u{302}", at('o', 1)),
202 ("r\u{30C}", at('r', 1)),
203 ("s\u{30C}", at('s', 1)),
204 ("z\u{30C}", at('z', 1)),
205];
206
207static PL: Rules = &[
208 ("a\u{328}", at('a', 1)),
209 ("c\u{301}", at('c', 1)),
210 ("e\u{328}", at('e', 1)),
211 ("ł", at('l', 1)),
212 ("n\u{301}", at('n', 1)),
213 ("o\u{301}", at('o', 1)),
214 ("s\u{301}", at('s', 1)),
215 ("z\u{301}", at('z', 1)),
216 ("z\u{307}", at('z', 2)),
217];
218
219static TR: Rules = &[
220 ("c\u{327}", at('c', 1)),
221 ("g\u{306}", at('g', 1)),
222 ("ı", at('i', -1)),
223 ("o\u{308}", at('o', 1)),
224 ("s\u{327}", at('s', 1)),
225 ("u\u{308}", at('u', 1)),
226];
227
228fn language(tag: &str) -> &str {
230 let l = tag.split(['-', '_', '.', '@']).next().unwrap_or("");
231 if l.eq_ignore_ascii_case("no") || l.eq_ignore_ascii_case("nn") { "nb" } else { l }
232}
233
234fn turkic(tag: &str) -> bool {
235 let l = language(tag);
236 l.eq_ignore_ascii_case("tr") || l.eq_ignore_ascii_case("az")
237}
238
239#[derive(Clone, Copy)]
241pub struct Collator {
242 rules: Rules,
243 german: bool,
244 turkish: bool,
245 phonebook: bool,
246 numeric: bool,
247 strength: u32,
248}
249
250impl Collator {
251 pub fn new(tag: &str) -> Collator {
255 let l = language(tag).to_ascii_lowercase();
256 let rules: Rules = match l.as_str() {
257 "sv" => SV,
258 "fi" => FI,
259 "da" | "nb" => DA,
260 "es" => ES,
261 "cs" => CS,
262 "sk" => SK,
263 "pl" => PL,
264 "tr" => TR,
265 _ => &[],
266 };
267 let mut c = Collator { rules, german: l == "de", turkish: l == "tr" || l == "az", phonebook: false, numeric: false, strength: COLL_TERTIARY };
268 let low = tag.to_ascii_lowercase();
269 let parts: Vec<&str> = low.split(['-', '_']).collect();
270 c.phonebook = low.contains("collation=phonebook") || parts.contains(&"phoneb");
271 if let Some(u) = parts.iter().position(|p| *p == "u") {
272 let ext = &parts[u + 1..];
273 for (k, key) in ext.iter().enumerate() {
274 let val = ext.get(k + 1).copied().filter(|v| v.len() > 2).unwrap_or("");
275 match *key {
276 "co" => c.phonebook = val == "phonebk",
277 "kn" => c.numeric = val != "false",
278 "ks" => {
279 c.strength = match val {
280 "level1" => COLL_PRIMARY,
281 "level2" => COLL_SECONDARY,
282 "identic" => COLL_IDENTICAL,
283 _ => COLL_TERTIARY,
284 }
285 }
286 _ => {}
287 }
288 }
289 }
290 c
291 }
292
293 pub fn strength(mut self, s: u32) -> Collator {
295 self.strength = s.clamp(COLL_PRIMARY, COLL_IDENTICAL);
296 self
297 }
298
299 pub fn numeric(mut self, on: bool) -> Collator {
301 self.numeric = on;
302 self
303 }
304
305 pub fn phonebook(mut self, on: bool) -> Collator {
307 self.phonebook = on;
308 self
309 }
310
311 pub fn compare(&self, a: &str, b: &str) -> Ordering {
313 if a == b {
314 return Ordering::Equal;
315 }
316 self.key(a).cmp(&self.key(b))
317 }
318
319 pub fn key(&self, s: &str) -> Vec<u32> {
321 let ces = self.elements(s);
322 let mut k: Vec<u32> = ces.iter().filter(|e| e.p != 0).map(|e| e.p).collect();
323 if self.strength >= COLL_SECONDARY {
324 k.push(0);
325 k.extend(ces.iter().filter(|e| e.s != 0).map(|e| e.s));
326 }
327 if self.strength >= COLL_TERTIARY {
328 k.push(0);
329 k.extend(ces.iter().filter(|e| e.p != 0).map(|e| e.t));
330 }
331 if self.strength >= COLL_IDENTICAL {
332 k.push(0);
333 k.extend(s.chars().map(|c| c as u32 + 1));
334 }
335 k
336 }
337
338 pub fn sort<T>(&self, items: &mut [T], text: impl Fn(&T) -> &str) {
340 items.sort_by_cached_key(|x| self.key(text(x)));
341 }
342
343 fn rules(&self) -> Rules {
344 if self.german && self.phonebook { DE_PHONEBOOK } else { self.rules }
345 }
346
347 fn chars(&self, s: &str) -> Vec<Ch> {
349 let mut out: Vec<Ch> = Vec::with_capacity(s.len());
350 for c in s.chars() {
351 let (c, var) = match c as u32 {
352 0xFF01..=0xFF5E => (char::from_u32(c as u32 - 0xFEE0).unwrap_or(c), true), 0x30A1..=0x30F6 => (char::from_u32(c as u32 - 0x60).unwrap_or(c), true), _ => (c, false),
355 };
356 if let Some(letters) = compat(c) {
357 for x in letters.chars() {
358 self.push_char(&mut out, x, true);
359 }
360 } else {
361 self.push_char(&mut out, c, var);
362 }
363 }
364 out
365 }
366
367 fn push_char(&self, out: &mut Vec<Ch>, c: char, var: bool) {
368 if self.turkish && (c == 'I' || c == 'İ') {
369 out.push(Ch { c: if c == 'I' { 'ı' } else { 'i' }, upper: true, var });
370 return;
371 }
372 let upper = c.to_lowercase().next() != Some(c);
373 for l in c.to_lowercase() {
374 let (l, v) = match single(l.to_uppercase()).and_then(|u| single(u.to_lowercase())) {
376 Some(f) if f != l && l != 'ı' => (f, true),
377 _ => (l, false),
378 };
379 let mut first = true;
380 decompose(l, &mut |d| {
381 out.push(if first { Ch { c: d, upper, var: var || v } } else { Ch { c: d, upper: false, var: false } });
382 first = false;
383 });
384 }
385 }
386
387 fn elements(&self, s: &str) -> Vec<Ce> {
389 let chs = self.chars(s);
390 let mut ces: Vec<Ce> = Vec::with_capacity(chs.len() + 4);
391 let mut i = 0;
392 while i < chs.len() {
393 if let Some((n, t)) = rule_at(self.rules(), &chs[i..]).or_else(|| rule_at(ROOT, &chs[i..])) {
394 let tert = tertiary(chs[i].upper, chs[i].var || t.var);
395 ces.push(Ce { p: primary_of(t.base).wrapping_add_signed(t.step as i32), s: S_COMMON, t: tert });
396 if t.sec != 0 {
397 ces.push(Ce { p: 0, s: t.sec, t: 0 });
398 }
399 if t.then != '\0' {
400 ces.push(Ce { p: primary_of(t.then), s: S_COMMON, t: tert });
401 }
402 i += n;
403 continue;
404 }
405 let ch = chs[i];
406 i += 1;
407 if is_mark(ch.c) {
408 ces.push(Ce { p: 0, s: mark_weight(ch.c), t: 0 });
409 } else if ch.c.is_whitespace() {
410 ces.push(Ce { p: P_SPACE + ch.c as u32, s: S_COMMON, t: tertiary(false, ch.var) });
411 } else if ignorable(ch.c) {
412 } else if let Some(d) = digit(ch.c) {
414 if !self.numeric {
415 ces.push(Ce { p: P_DIGIT + d, s: S_COMMON, t: tertiary(false, ch.var || !ch.c.is_ascii_digit()) });
416 continue;
417 }
418 let mut digits = alloc::vec![(d, ch)];
421 while let Some(d) = chs.get(i).and_then(|c| digit(c.c).map(|d| (d, *c))) {
422 digits.push(d);
423 i += 1;
424 }
425 let zeros = digits.iter().take(digits.len() - 1).take_while(|d| d.0 == 0).count();
426 let sig = &digits[zeros..];
427 ces.push(Ce { p: P_NUMBER + sig.len() as u32, s: S_COMMON, t: 1 + zeros as u32 });
428 for (d, c) in sig {
429 ces.push(Ce { p: P_DIGIT + d, s: S_COMMON, t: tertiary(false, c.var || !c.c.is_ascii_digit()) });
430 }
431 } else {
432 let p = if ch.c.is_alphabetic() { primary_of(ch.c) } else { P_PUNCT + ch.c as u32 };
433 ces.push(Ce { p, s: S_COMMON, t: tertiary(ch.upper, ch.var) });
434 }
435 }
436 ces
437 }
438}
439
440#[derive(Clone, Copy)]
442struct Ch {
443 c: char,
444 upper: bool,
445 var: bool,
446}
447
448struct Ce {
450 p: u32,
451 s: u32,
452 t: u32,
453}
454
455fn tertiary(upper: bool, var: bool) -> u32 {
457 1 + var as u32 + 2 * upper as u32
458}
459
460fn primary_of(c: char) -> u32 {
462 match c {
463 'a'..='z' => P_LATIN + (c as u32 - 'a' as u32 + 1) * 32,
464 _ => P_OTHER + c as u32 * 4,
465 }
466}
467
468fn rule_at(rules: Rules, chs: &[Ch]) -> Option<(usize, Tail)> {
470 let mut best: Option<(usize, Tail)> = None;
471 for (pat, t) in rules {
472 let mut n = 0;
473 let ok = pat.chars().all(|p| {
474 let hit = chs.get(n).is_some_and(|c| c.c == p);
475 n += 1;
476 hit
477 });
478 if ok && best.is_none_or(|b| n > b.0) {
479 best = Some((n, *t));
480 }
481 }
482 best
483}
484
485fn single(mut it: impl Iterator<Item = char>) -> Option<char> {
486 let c = it.next()?;
487 if it.next().is_none() { Some(c) } else { None }
488}
489
490fn decompose(c: char, out: &mut impl FnMut(char)) {
492 let cp = c as u32;
493 if cp >= 0xC0 && cp <= 0xFFFF {
494 if let Ok(k) = DECOMP.binary_search_by_key(&(cp as u16), |e| e.0) {
495 let (_, a, b) = DECOMP[k];
496 decompose(char::from_u32(a as u32).unwrap_or(c), out);
497 if b != 0 {
498 decompose(char::from_u32(b as u32).unwrap_or(c), out);
499 }
500 return;
501 }
502 }
503 out(c);
504}
505
506fn compat(c: char) -> Option<&'static str> {
508 let cp = c as u32;
509 if !(0xAA..=0xFFFF).contains(&cp) {
510 return None;
511 }
512 COMPAT.binary_search_by_key(&(cp as u16), |e| e.0).ok().map(|k| COMPAT[k].1)
513}
514
515fn is_mark(c: char) -> bool {
516 let cp = c as u32;
517 if !(0x300..=0xFFFF).contains(&cp) {
518 return false;
519 }
520 let k = MARKS.partition_point(|r| (r.1 as u32) < cp);
521 k < MARKS.len() && MARKS[k].0 as u32 <= cp
522}
523
524fn ignorable(c: char) -> bool {
525 c.is_control()
526 || matches!(c, '\u{AD}' | '\u{34F}' | '\u{61C}' | '\u{180E}' | '\u{200B}'..='\u{200F}' | '\u{202A}'..='\u{202E}'
527 | '\u{2060}'..='\u{206F}' | '\u{FE00}'..='\u{FE0F}' | '\u{FEFF}')
528}
529
530fn digit(c: char) -> Option<u32> {
532 let cp = c as u32;
533 if c.is_ascii_digit() {
534 return Some(cp - '0' as u32);
535 }
536 if cp < 0x660 {
537 return None;
538 }
539 let k = ZEROS.partition_point(|&z| z <= cp);
540 (k > 0 && cp - ZEROS[k - 1] <= 9).then(|| cp - ZEROS[k - 1])
541}
542
543pub fn collator() -> Collator {
545 Collator::new(&super::locale_tag())
546}
547
548pub fn collate(a: &str, b: &str) -> Ordering {
550 collator().compare(a, b)
551}
552
553impl Locale {
554 pub fn collate(&self, a: &str, b: &str) -> Ordering {
556 self.collator().compare(a, b)
557 }
558 pub fn collator(&self) -> Collator {
560 Collator::new(self.tag())
561 }
562}
563
564pub fn to_upper(s: &str) -> String {
568 to_upper_in(&super::locale_tag(), s)
569}
570
571pub fn to_lower(s: &str) -> String {
573 to_lower_in(&super::locale_tag(), s)
574}
575
576pub fn fold(s: &str) -> String {
579 fold_in(&super::locale_tag(), s)
580}
581
582pub fn eq_ignore_case(a: &str, b: &str) -> bool {
585 a == b || nfd(&fold(a)) == nfd(&fold(b))
586}
587
588pub fn to_upper_in(lang: &str, s: &str) -> String {
590 if turkic(lang) && s.contains('i') { s.replace('i', "İ").to_uppercase() } else { s.to_uppercase() }
591}
592
593pub fn to_lower_in(lang: &str, s: &str) -> String {
595 if turkic(lang) && s.contains(['I', 'İ']) {
596 s.replace("I\u{307}", "i").replace('I', "ı").replace('İ', "i").to_lowercase()
597 } else {
598 s.to_lowercase()
599 }
600}
601
602pub fn fold_in(lang: &str, s: &str) -> String {
605 let tr = turkic(lang);
606 let mut out = String::with_capacity(s.len());
607 for c in s.chars() {
608 match c {
609 'I' if tr => out.push('ı'),
610 'İ' if tr => out.push('i'),
611 'ı' => out.push(c),
612 '\u{13A0}'..='\u{13F5}' | '\u{13F8}'..='\u{13FD}' | '\u{AB70}'..='\u{ABBF}' => out.extend(c.to_uppercase()),
614 _ => {
615 for u in c.to_uppercase() {
616 for l in u.to_lowercase() {
617 if l == 'ß' { out.push_str("ss") } else { out.push(l) }
618 }
619 }
620 }
621 }
622 }
623 out
624}
625
626fn nfd(s: &str) -> String {
628 let mut out = String::with_capacity(s.len());
629 for c in s.chars() {
630 decompose(c, &mut |d| out.push(d));
631 }
632 out
633}