Skip to main content

icu_collator/
lib.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5// Various collation-related algorithms and constants in this file are
6// adapted from ICU4C and, therefore, are subject to the ICU license as
7// described in LICENSE.
8
9// https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations
10#![cfg_attr(not(any(test, doc)), no_std)]
11#![cfg_attr(
12    not(test),
13    deny(
14        clippy::indexing_slicing,
15        clippy::unwrap_used,
16        clippy::expect_used,
17        clippy::panic,
18    )
19)]
20#![warn(missing_docs)]
21
22//! Comparing strings according to language-dependent conventions.
23//!
24//! This module is published as its own crate ([`icu_collator`](https://docs.rs/icu_collator/latest/icu_collator/))
25//! and as part of the [`icu`](https://docs.rs/icu/latest/icu/) crate. See the latter for more details on the ICU4X project.
26//! `Collator` is the main structure of the component. It accepts a set of arguments
27//! which allow it to collect necessary data from the data provider, and once
28//! instantiated, can be used to compare strings.
29//!
30//! Refer to the ICU User Guide sections for Collation that give an
31//! [introduction](https://unicode-org.github.io/icu/userguide/collation/) and explain
32//! [basic concepts](https://unicode-org.github.io/icu/userguide/collation/concepts.html).
33//!
34//! # Examples
35//!
36//! As its most basic purpose, `Collator` offers locale-aware ordering:
37//!
38//! ```
39//! use core::cmp::Ordering;
40//! use icu::collator::{options::*, *};
41//! use icu::locale::locale;
42//!
43//! let mut options = CollatorOptions::default();
44//! options.strength = Some(Strength::Primary);
45//! let collator_es =
46//!     Collator::try_new(locale!("es-u-co-trad").into(), options).unwrap();
47//!
48//! // "pollo" > "polvo" in traditional Spanish
49//! assert_eq!(collator_es.compare("pollo", "polvo"), Ordering::Greater);
50//!
51//! let mut options = CollatorOptions::default();
52//! options.strength = Some(Strength::Primary);
53//! let collator_en = Collator::try_new(locale!("en").into(), options).unwrap();
54//!
55//! // "pollo" < "polvo" according to English rules
56//! assert_eq!(collator_en.compare("pollo", "polvo"), Ordering::Less);
57//! ```
58//!
59//! ## Examples of `CollatorOptions`
60//!
61//! The [`CollatorOptions`] struct configures specific custom behavior for the `Collator`.  See docs
62//! for [`CollatorOptions`] for more details.  Some basic descriptions and examples are below.
63//!
64//! ## Strength
65//!
66//! The collation strength indicates how many levels to compare. The primary
67//! level considers base letters, i.e. 'a' and 'b' are unequal but 'E' and 'é'
68//! are equal, with higher levels dealing with distinctions such as accents
69//! and case.
70//!
71//! If an lower level isn't equal, the lower level is decisive.
72//! If the comparison result is equal on one level,
73//! but the collator's strength input value is higher than that,
74//! then the collator comparison iteratively proceeds to the next higher level.
75//!
76//! Note that lowering the strength value given to the collator means that more user-perceptible
77//!  differences will compare as equal. This may make sense when sorting more complex structures
78//! where the string to be compared is just one field, and ties between strings
79//! that differ only in case, accent, or similar are resolved by comparing some
80//! secondary field in the larger structure to be sorted.
81//!
82//! Therefore, if the sort is just a string sort without some other field for
83//! resolving ties, lowering the strength means that factors that don't make
84//! sense to the user (such as the order of items prior to sorting with a stable
85//! sort algorithm or the internal details of a sorting algorithm that doesn't
86//! provide the stability property) affect the relative order of strings that
87//! do have user-perceptible differences particularly in accents or case.
88//!
89//! Lowering the strength is less of a perfomance optimization than it may seem
90//! directly from the above description. As described above, in the case
91//! of identical strings to be compared, the algorithm has to work though all
92//! the levels, from primary up to the provided strength value given to collator, without an early exit. However, this
93//! collator implements an identical prefix optimization, which examines the
94//! code units of the strings to be compared to skip the identical prefix before
95//! starting the actual collation algorithm. When the strings to be compared
96//! are identical on the byte level, they are found to be equal without the
97//! actual collation algorithm running at all! Therefore, the strength setting
98//! only has an effect (whether order effect or performance effect) for
99//! comparisons where the strings to be compared are not equal on the byte level
100//! but are equal on the primary level/strength. The common cases are that
101//! a comparison is decided on the primary level or the strings are byte
102//! equal, which narrows the performance effect of lowering the strength
103//! setting.
104//!
105//! ```
106//! use core::cmp::Ordering;
107//! use icu::collator::{options::*, *};
108//!
109//! // Primary Level
110//!
111//! let mut options_l1 = CollatorOptions::default();
112//! options_l1.strength = Some(Strength::Primary);
113//! let collator_l1 =
114//!     Collator::try_new(Default::default(), options_l1).unwrap();
115//!
116//! assert_eq!(collator_l1.compare("a", "b"), Ordering::Less); // primary
117//! assert_eq!(collator_l1.compare("as", "às"), Ordering::Equal); // secondary
118//! assert_eq!(collator_l1.compare("às", "at"), Ordering::Less);
119//! assert_eq!(collator_l1.compare("ao", "Ao"), Ordering::Equal); // tertiary
120//! assert_eq!(collator_l1.compare("Ao", "aò"), Ordering::Equal);
121//! assert_eq!(collator_l1.compare("A", "Ⓐ"), Ordering::Equal);
122//!
123//! // Secondary Level
124//!
125//! let mut options_l2 = CollatorOptions::default();
126//! options_l2.strength = Some(Strength::Secondary);
127//! let collator_l2 =
128//!     Collator::try_new(Default::default(), options_l2).unwrap();
129//!
130//! assert_eq!(collator_l2.compare("a", "b"), Ordering::Less); // primary
131//! assert_eq!(collator_l2.compare("as", "às"), Ordering::Less); // secondary
132//! assert_eq!(collator_l2.compare("às", "at"), Ordering::Less);
133//! assert_eq!(collator_l2.compare("ao", "Ao"), Ordering::Equal); // tertiary
134//! assert_eq!(collator_l2.compare("Ao", "aò"), Ordering::Less);
135//! assert_eq!(collator_l2.compare("A", "Ⓐ"), Ordering::Equal);
136//!
137//! // Tertiary Level
138//!
139//! let mut options_l3 = CollatorOptions::default();
140//! options_l3.strength = Some(Strength::Tertiary);
141//! let collator_l3 =
142//!     Collator::try_new(Default::default(), options_l3).unwrap();
143//!
144//! assert_eq!(collator_l3.compare("a", "b"), Ordering::Less); // primary
145//! assert_eq!(collator_l3.compare("as", "às"), Ordering::Less); // secondary
146//! assert_eq!(collator_l3.compare("às", "at"), Ordering::Less);
147//! assert_eq!(collator_l3.compare("ao", "Ao"), Ordering::Less); // tertiary
148//! assert_eq!(collator_l3.compare("Ao", "aò"), Ordering::Less);
149//! assert_eq!(collator_l3.compare("A", "Ⓐ"), Ordering::Less);
150//! ```
151//!
152//! ## Alternate Handling
153//!
154//! Allows alternate handling for certain customized collation orderings, including the option to
155//! ignore the special handling for the strings of such customizations.  Specifically,
156//! alternate handling is used to control the handling of the so-called **variable** characters in the
157//! Unicode Collation Algorithm: whitespace, punctuation and symbols.
158//!
159//! Note that `AlternateHandling::ShiftTrimmed` and `AlternateHandling::Blanked` are
160//! unimplemented. The default is `AlternateHandling::NonIgnorable`, except
161//! for Thai, whose default is `AlternateHandling::Shifted`.
162//!
163//! ```
164//! use core::cmp::Ordering;
165//! use icu::collator::{*, options::*};
166//!
167//! // If alternate handling is set to `NonIgnorable`, then differences among
168//! // these characters are of the same importance as differences among letters.
169//!
170//! let mut options_3n = CollatorOptions::default();
171//! options_3n.strength = Some(Strength::Tertiary);
172//! options_3n.alternate_handling = Some(AlternateHandling::NonIgnorable);
173//! let collator_3n =
174//!     Collator::try_new(Default::default(), options_3n).unwrap();
175//!
176//! assert_eq!(collator_3n.compare("di Silva", "Di Silva"), Ordering::Less);
177//! assert_eq!(collator_3n.compare("Di Silva", "diSilva"), Ordering::Less);
178//! assert_eq!(collator_3n.compare("diSilva", "U.S.A."), Ordering::Less);
179//! assert_eq!(collator_3n.compare("U.S.A.", "USA"), Ordering::Less);
180//!
181//! // If alternate handling is set to `Shifted`, then these characters are of only minor
182//! // importance. The Shifted value is often used in combination with Strength
183//! // set to Quaternary.
184//!
185//! let mut options_3s = CollatorOptions::default();
186//! options_3s.strength = Some(Strength::Tertiary);
187//! options_3s.alternate_handling = Some(AlternateHandling::Shifted);
188//! let collator_3s =
189//!     Collator::try_new(Default::default(), options_3s).unwrap();
190//!
191//! assert_eq!(collator_3s.compare("di Silva", "diSilva"), Ordering::Equal);
192//! assert_eq!(collator_3s.compare("diSilva", "Di Silva"), Ordering::Less);
193//! assert_eq!(collator_3s.compare("Di Silva", "U.S.A."), Ordering::Less);
194//! assert_eq!(collator_3s.compare("U.S.A.", "USA"), Ordering::Equal);
195//!
196//! let mut options_4s = CollatorOptions::default();
197//! options_4s.strength = Some(Strength::Quaternary);
198//! options_4s.alternate_handling = Some(AlternateHandling::Shifted);
199//! let collator_4s =
200//!     Collator::try_new(Default::default(), options_4s).unwrap();
201//!
202//! assert_eq!(collator_4s.compare("di Silva", "diSilva"), Ordering::Less);
203//! assert_eq!(collator_4s.compare("diSilva", "Di Silva"), Ordering::Less);
204//! assert_eq!(collator_4s.compare("Di Silva", "U.S.A."), Ordering::Less);
205//! assert_eq!(collator_4s.compare("U.S.A.", "USA"), Ordering::Less);
206//! ```
207//!
208//! ## Case Level
209//!
210//! Whether to distinguish case in sorting, even for sorting levels higher than tertiary,
211//! without having to use tertiary level just to enable case level differences.
212//!
213//! ```
214//! use core::cmp::Ordering;
215//! use icu::collator::{*, options::*};
216//!
217//! // Primary
218//!
219//! let mut options = CollatorOptions::default();
220//! options.strength = Some(Strength::Primary);
221//! options.case_level = Some(CaseLevel::Off);
222//! let primary =
223//!   Collator::try_new(Default::default(),
224//!                     options).unwrap();
225//!
226//! assert_eq!(primary.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Equal);
227//! assert_eq!(primary.compare("dejavu", "dejAvu"), Ordering::Equal);
228//! assert_eq!(primary.compare("dejavu", "déjavu"), Ordering::Equal);
229//!
230//! // Primary with case level on
231//!
232//! options.strength = Some(Strength::Primary);
233//! options.case_level = Some(CaseLevel::On);
234//! let primary_and_case =
235//!   Collator::try_new(Default::default(),
236//!                     options).unwrap();
237//!
238//! assert_eq!(primary_and_case.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less);
239//! assert_eq!(primary_and_case.compare("dejavu", "dejAvu"), Ordering::Less);
240//! assert_eq!(primary_and_case.compare("dejavu", "déjavu"), Ordering::Equal);
241//!
242//! // Secondary with case level on
243//!
244//! options.strength = Some(Strength::Secondary);
245//! options.case_level = Some(CaseLevel::On);
246//! let secondary_and_case =
247//!   Collator::try_new(Default::default(),
248//!                     options).unwrap();
249//!
250//! assert_eq!(secondary_and_case.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less);
251//! assert_eq!(secondary_and_case.compare("dejavu", "dejAvu"), Ordering::Less);
252//! assert_eq!(secondary_and_case.compare("dejavu", "déjavu"), Ordering::Less);  // secondary difference
253//!
254//! // Tertiary
255//!
256//! options.strength = Some(Strength::Tertiary);
257//! options.case_level = Some(CaseLevel::Off);
258//! let tertiary =
259//!   Collator::try_new(Default::default(),
260//!                     options).unwrap();
261//!
262//! assert_eq!(tertiary.compare("ⓓⓔⓐⓛ", "DEAL"), Ordering::Less);
263//! assert_eq!(tertiary.compare("dejavu", "dejAvu"), Ordering::Less);
264//! assert_eq!(tertiary.compare("dejavu", "déjavu"), Ordering::Less);
265//! ```
266//!
267//!
268//! ## Backward second level
269//!
270//! Compare the second level in backward order. The default is `false` (off), except for Canadian
271//! French.
272//!
273//! ## Examples of `CollatorPreferences`
274//!
275//! The [`CollatorPreferences`] struct configures specific custom behavior for the `Collator`, like
276//! [`CollatorOptions`]. However, unlike `CollatorOptions`, this set of preferences can also be set
277//! implicitly by the locale. See docs for [`CollatorPreferences`] for more details.
278//! Some basic descriptions and examples are below.
279//!
280//! ## Case First
281//!
282//! Whether to swap the ordering of uppercase and lowercase.
283//!
284//! ```
285//! use core::cmp::Ordering;
286//! use icu::collator::preferences::*;
287//! use icu::collator::{options::*, *};
288//!
289//! // Use the locale's default.
290//!
291//! let mut prefs_no_case = CollatorPreferences::default();
292//! prefs_no_case.case_first = Some(CollationCaseFirst::False);
293//! let collator_no_case =
294//!     Collator::try_new(prefs_no_case, Default::default()).unwrap();
295//! assert_eq!(collator_no_case.compare("ab", "AB"), Ordering::Less);
296//!
297//! // Lowercase is less
298//!
299//! let mut prefs_lower_less = CollatorPreferences::default();
300//! prefs_lower_less.case_first = Some(CollationCaseFirst::Lower);
301//! let collator_lower_less =
302//!     Collator::try_new(prefs_lower_less, Default::default()).unwrap();
303//! assert_eq!(collator_lower_less.compare("ab", "AB"), Ordering::Less);
304//!
305//! // Uppercase is less
306//!
307//! let mut prefs_upper_greater = CollatorPreferences::default();
308//! prefs_upper_greater.case_first = Some(CollationCaseFirst::Upper);
309//! let collator_upper_greater =
310//!     Collator::try_new(prefs_upper_greater, Default::default()).unwrap();
311//! assert_eq!(collator_upper_greater.compare("AB", "ab"), Ordering::Less);
312//! ```
313//!
314//! ## Numeric
315//!
316//! When set to `true` (on), any sequence of decimal
317//! digits is sorted at a primary level according to the
318//! numeric value.
319//!
320//! ```
321//! use core::cmp::Ordering;
322//! use icu::collator::preferences::*;
323//! use icu::collator::{options::*, *};
324//!
325//! // Numerical sorting off
326//!
327//! let mut prefs_num_off = CollatorPreferences::default();
328//! prefs_num_off.numeric_ordering = Some(CollationNumericOrdering::False);
329//! let collator_num_off =
330//!     Collator::try_new(prefs_num_off, Default::default()).unwrap();
331//! assert_eq!(collator_num_off.compare("a10b", "a2b"), Ordering::Less);
332//!
333//! // Numerical sorting on
334//!
335//! let mut prefs_num_on = CollatorPreferences::default();
336//! prefs_num_on.numeric_ordering = Some(CollationNumericOrdering::True);
337//! let collator_num_on =
338//!     Collator::try_new(prefs_num_on, Default::default()).unwrap();
339//! assert_eq!(collator_num_on.compare("a10b", "a2b"), Ordering::Greater);
340//! ```
341//!
342//! [`CollatorOptions`]: options::CollatorOptions
343
344extern crate alloc;
345
346mod comparison;
347#[cfg(doc)]
348pub mod docs;
349
350// NOTE: The Pernosco debugger has special knowledge
351// of the `CharacterAndClass` struct inside the `elements`
352// module. Please do not change the crate-module-qualified
353// name of that struct without coordination.
354mod elements;
355
356pub mod options;
357pub mod provider;
358
359pub use comparison::Collator;
360pub use comparison::CollatorBorrowed;
361pub use comparison::CollatorPreferences;
362
363#[cfg(feature = "datagen")]
364pub use elements::is_self_contained;
365
366#[cfg(feature = "unstable")]
367pub use comparison::CollationKeySink;
368
369/// Locale preferences used by this crate
370pub mod preferences {
371    /// **This is a reexport of a type in [`icu::locale`](icu_locale_core::preferences::extensions::unicode::keywords)**.
372    #[doc = "\n"] // prevent autoformatting
373    pub use icu_locale_core::preferences::extensions::unicode::keywords::CollationCaseFirst;
374    /// **This is a reexport of a type in [`icu::locale`](icu_locale_core::preferences::extensions::unicode::keywords)**.
375    #[doc = "\n"] // prevent autoformatting
376    pub use icu_locale_core::preferences::extensions::unicode::keywords::CollationNumericOrdering;
377    /// **This is a reexport of a type in [`icu::locale`](icu_locale_core::preferences::extensions::unicode::keywords)**.
378    #[doc = "\n"] // prevent autoformatting
379    pub use icu_locale_core::preferences::extensions::unicode::keywords::CollationType;
380}