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 ).
5 use core
::borrow
::Borrow
;
6 use core
::cmp
::Ordering
;
7 use core
::iter
::FromIterator
;
12 use crate::helpers
::ShortVec
;
13 use crate::ordering
::SubtagOrderingResult
;
15 /// A list of [`Key`]-[`Value`] pairs representing functional information
16 /// about locale's internationnalization preferences.
18 /// Here are examples of fields used in Unicode:
19 /// - `hc` - Hour Cycle (`h11`, `h12`, `h23`, `h24`)
20 /// - `ca` - Calendar (`buddhist`, `gregory`, ...)
21 /// - `fw` - First Day Of the Week (`sun`, `mon`, `sat`, ...)
23 /// You can find the full list in [`Unicode BCP 47 U Extension`] section of LDML.
25 /// [`Unicode BCP 47 U Extension`]: https://unicode.org/reports/tr35/tr35.html#Key_And_Type_Definitions_
29 /// Manually build up a [`Keywords`] object:
33 /// extensions::unicode::Keywords, extensions_unicode_key as key,
34 /// extensions_unicode_value as value, locale,
37 /// let keywords = vec![(key!("hc"), value!("h23"))]
39 /// .collect::<Keywords>();
41 /// assert_eq!(&keywords.to_string(), "hc-h23");
44 /// Access a [`Keywords`] object from a [`Locale`]:
48 /// extensions_unicode_key as key, extensions_unicode_value as value,
52 /// let loc: Locale = "und-u-hc-h23-kc-true".parse().expect("Valid BCP-47");
54 /// assert_eq!(loc.extensions.unicode.keywords.get(&key!("ca")), None);
56 /// loc.extensions.unicode.keywords.get(&key!("hc")),
57 /// Some(&value!("h23"))
60 /// loc.extensions.unicode.keywords.get(&key!("kc")),
61 /// Some(&value!("true"))
64 /// assert_eq!(loc.extensions.unicode.keywords.to_string(), "hc-h23-kc");
67 /// [`Locale`]: crate::Locale
68 #[derive(Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)]
69 pub struct Keywords(LiteMap
<Key
, Value
, ShortVec
<(Key
, Value
)>>);
72 /// Returns a new empty list of key-value pairs. Same as [`default()`](Default::default()), but is `const`.
77 /// use icu::locid::extensions::unicode::Keywords;
79 /// assert_eq!(Keywords::new(), Keywords::default());
82 pub const fn new() -> Self {
86 /// Create a new list of key-value pairs having exactly one pair, callable in a `const` context.
88 pub const fn new_single(key
: Key
, value
: Value
) -> Self {
89 Self(LiteMap
::from_sorted_store_unchecked(ShortVec
::new_single(
94 /// Returns `true` if there are no keywords.
99 /// use icu::locid::extensions::unicode::Keywords;
100 /// use icu::locid::locale;
101 /// use icu::locid::Locale;
103 /// let loc1 = Locale::try_from_bytes(b"und-t-h0-hybrid").unwrap();
104 /// let loc2 = locale!("und-u-ca-buddhist");
106 /// assert!(loc1.extensions.unicode.keywords.is_empty());
107 /// assert!(!loc2.extensions.unicode.keywords.is_empty());
109 pub fn is_empty(&self) -> bool
{
113 /// Returns `true` if the list contains a [`Value`] for the specified [`Key`].
119 /// use icu::locid::{
120 /// extensions::unicode::Keywords, extensions_unicode_key as key,
121 /// extensions_unicode_value as value,
124 /// let keywords = vec![(key!("ca"), value!("gregory"))]
126 /// .collect::<Keywords>();
128 /// assert!(&keywords.contains_key(&key!("ca")));
130 pub fn contains_key
<Q
>(&self, key
: &Q
) -> bool
135 self.0.contains_key(key
)
138 /// Returns a reference to the [`Value`] corresponding to the [`Key`].
144 /// use icu::locid::{
145 /// extensions::unicode::Keywords, extensions_unicode_key as key,
146 /// extensions_unicode_value as value,
149 /// let keywords = vec![(key!("ca"), value!("buddhist"))]
151 /// .collect::<Keywords>();
153 /// assert_eq!(keywords.get(&key!("ca")), Some(&value!("buddhist")));
155 pub fn get
<Q
>(&self, key
: &Q
) -> Option
<&Value
>
163 /// Returns a mutable reference to the [`Value`] corresponding to the [`Key`].
165 /// Returns `None` if the key doesn't exist or if the key has no value.
170 /// use icu::locid::{
171 /// extensions::unicode::Keywords, extensions_unicode_key as key,
172 /// extensions_unicode_value as value,
175 /// let mut keywords = vec![(key!("ca"), value!("buddhist"))]
177 /// .collect::<Keywords>();
179 /// if let Some(value) = keywords.get_mut(&key!("ca")) {
180 /// *value = value!("gregory");
182 /// assert_eq!(keywords.get(&key!("ca")), Some(&value!("gregory")));
184 pub fn get_mut
<Q
>(&mut self, key
: &Q
) -> Option
<&mut Value
>
192 /// Sets the specified keyword, returning the old value if it already existed.
197 /// use icu::locid::extensions::unicode::Key;
198 /// use icu::locid::extensions::unicode::Value;
199 /// use icu::locid::Locale;
200 /// use icu::locid::{
201 /// extensions_unicode_key as key, extensions_unicode_value as value,
204 /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12"
206 /// .expect("valid BCP-47 identifier");
207 /// let old_value = loc
211 /// .set(key!("ca"), value!("japanese"));
213 /// assert_eq!(old_value, Some(value!("buddhist")));
214 /// assert_eq!(loc, "und-u-hello-ca-japanese-hc-h12".parse().unwrap());
216 pub fn set(&mut self, key
: Key
, value
: Value
) -> Option
<Value
> {
217 self.0.insert
(key
, value
)
220 /// Removes the specified keyword, returning the old value if it existed.
225 /// use icu::locid::extensions::unicode::Key;
226 /// use icu::locid::extensions_unicode_key as key;
227 /// use icu::locid::Locale;
229 /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12"
231 /// .expect("valid BCP-47 identifier");
232 /// loc.extensions.unicode.keywords.remove(key!("ca"));
233 /// assert_eq!(loc, "und-u-hello-hc-h12".parse().unwrap());
235 pub fn remove
<Q
: Borrow
<Key
>>(&mut self, key
: Q
) -> Option
<Value
> {
236 self.0.remove(key
.borrow())
239 /// Clears all Unicode extension keywords, leaving Unicode attributes.
241 /// Returns the old Unicode extension keywords.
246 /// use icu::locid::Locale;
248 /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12".parse().unwrap();
249 /// loc.extensions.unicode.keywords.clear();
250 /// assert_eq!(loc, "und-u-hello".parse().unwrap());
252 pub fn clear(&mut self) -> Self {
253 core
::mem
::take(self)
256 /// Retains a subset of keywords as specified by the predicate function.
261 /// use icu::locid::extensions_unicode_key as key;
262 /// use icu::locid::Locale;
264 /// let mut loc: Locale = "und-u-ca-buddhist-hc-h12-ms-metric".parse().unwrap();
269 /// .retain_by_key(|&k| k == key!("hc"));
270 /// assert_eq!(loc, "und-u-hc-h12".parse().unwrap());
275 /// .retain_by_key(|&k| k == key!("ms"));
276 /// assert_eq!(loc, Locale::UND);
278 pub fn retain_by_key
<F
>(&mut self, mut predicate
: F
)
280 F
: FnMut(&Key
) -> bool
,
282 self.0.retain(|k
, _
| predicate(k
))
285 /// Compare this [`Keywords`] with BCP-47 bytes.
287 /// The return value is equivalent to what would happen if you first converted this
288 /// [`Keywords`] to a BCP-47 string and then performed a byte comparison.
290 /// This function is case-sensitive and results in a *total order*, so it is appropriate for
291 /// binary search. The only argument producing [`Ordering::Equal`] is `self.to_string()`.
296 /// use icu::locid::extensions::unicode::Keywords;
297 /// use icu::locid::Locale;
298 /// use std::cmp::Ordering;
300 /// let bcp47_strings: &[&str] =
301 /// &["ca-hebrew", "ca-japanese", "ca-japanese-nu-latn", "nu-latn"];
303 /// for ab in bcp47_strings.windows(2) {
306 /// assert!(a.cmp(b) == Ordering::Less);
307 /// let a_kwds = format!("und-u-{}", a)
308 /// .parse::<Locale>()
313 /// assert!(a_kwds.strict_cmp(a.as_bytes()) == Ordering::Equal);
314 /// assert!(a_kwds.strict_cmp(b.as_bytes()) == Ordering::Less);
317 pub fn strict_cmp(&self, other
: &[u8]) -> Ordering
{
318 self.strict_cmp_iter(other
.split(|b
| *b
== b'
-'
)).end()
321 /// Compare this [`Keywords`] with an iterator of BCP-47 subtags.
323 /// This function has the same equality semantics as [`Keywords::strict_cmp`]. It is intended as
324 /// a more modular version that allows multiple subtag iterators to be chained together.
326 /// For an additional example, see [`SubtagOrderingResult`].
331 /// use icu::locid::extensions::unicode::Keywords;
332 /// use icu::locid::locale;
333 /// use std::cmp::Ordering;
335 /// let subtags: &[&[u8]] = &[b"ca", b"buddhist"];
337 /// let kwds = locale!("und-u-ca-buddhist").extensions.unicode.keywords;
340 /// kwds.strict_cmp_iter(subtags.iter().copied()).end()
343 /// let kwds = locale!("und").extensions.unicode.keywords;
346 /// kwds.strict_cmp_iter(subtags.iter().copied()).end()
349 /// let kwds = locale!("und-u-nu-latn").extensions.unicode.keywords;
351 /// Ordering::Greater,
352 /// kwds.strict_cmp_iter(subtags.iter().copied()).end()
355 pub fn strict_cmp_iter
<'l
, I
>(&self, mut subtags
: I
) -> SubtagOrderingResult
<I
>
357 I
: Iterator
<Item
= &'l
[u8]>,
359 let r
= self.for_each_subtag_str(&mut |subtag
| {
360 if let Some(other
) = subtags
.next() {
361 match subtag
.as_bytes().cmp(other
) {
362 Ordering
::Equal
=> Ok(()),
363 not_equal
=> Err(not_equal
),
366 Err(Ordering
::Greater
)
370 Ok(_
) => SubtagOrderingResult
::Subtags(subtags
),
371 Err(o
) => SubtagOrderingResult
::Ordering(o
),
375 pub(crate) fn for_each_subtag_str
<E
, F
>(&self, f
: &mut F
) -> Result
<(), E
>
377 F
: FnMut(&str) -> Result
<(), E
>,
379 for (k
, v
) in self.0.iter
() {
381 v
.for_each_subtag_str(f
)?
;
386 /// This needs to be its own method to help with type inference in helpers.rs
388 pub(crate) fn from_tuple_vec(v
: Vec
<(Key
, Value
)>) -> Self {
389 v
.into_iter().collect()
393 impl From
<LiteMap
<Key
, Value
, ShortVec
<(Key
, Value
)>>> for Keywords
{
394 fn from(map
: LiteMap
<Key
, Value
, ShortVec
<(Key
, Value
)>>) -> Self {
399 impl FromIterator
<(Key
, Value
)> for Keywords
{
400 fn from_iter
<I
: IntoIterator
<Item
= (Key
, Value
)>>(iter
: I
) -> Self {
401 LiteMap
::from_iter(iter
).into()
405 impl_writeable_for_key_value
!(Keywords
, "ca", "islamic-civil", "mm", "mm");