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 use core::str::FromStr; 6 7 use crate::parser::ParseError; 8 use crate::subtags::{Region, Subtag}; 9 10 impl_tinystr_subtag!( 11 /// A subdivision suffix used in [`SubdivisionId`]. 12 /// 13 /// This suffix represents a specific subdivision code under a given [`Region`]. 14 /// For example the value of [`SubdivisionId`] may be `gbsct`, where the [`SubdivisionSuffix`] 15 /// is `sct` for Scotland. 16 /// 17 /// Such a value associated with a key `rg` means that the locale should use Unit Preferences 18 /// (default calendar, currency, week data, time cycle, measurement system) for Scotland, even if the 19 /// [`LanguageIdentifier`](crate::LanguageIdentifier) is `en-US`. 20 /// 21 /// A subdivision suffix has to be a sequence of alphanumerical characters no 22 /// shorter than one and no longer than four characters. 23 /// 24 /// 25 /// # Examples 26 /// 27 /// ``` 28 /// use icu::locale::extensions::unicode::{subdivision_suffix, SubdivisionSuffix}; 29 /// 30 /// let ss: SubdivisionSuffix = 31 /// "sct".parse().expect("Failed to parse a SubdivisionSuffix."); 32 /// 33 /// assert_eq!(ss, subdivision_suffix!("sct")); 34 /// ``` 35 SubdivisionSuffix, 36 extensions::unicode, 37 subdivision_suffix, 38 extensions_unicode_subdivision_suffix, 39 1..=4, 40 s, 41 s.is_ascii_alphanumeric(), 42 s.to_ascii_lowercase(), 43 s.is_ascii_alphanumeric() && s.is_ascii_lowercase(), 44 InvalidExtension, 45 ["sct"], 46 ["toolooong"], 47 ); 48 49 /// A Subivision Id as defined in [`Unicode Locale Identifier`]. 50 /// 51 /// Subdivision Id is used in [`Unicode`] extensions: 52 /// * `rg` - Regional Override 53 /// * `sd` - Regional Subdivision 54 /// 55 /// In both cases the subdivision is composed of a [`Region`] and a [`SubdivisionSuffix`] which represents 56 /// different meaning depending on the key. 57 /// 58 /// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/tr35.html#unicode_subdivision_id 59 /// [`Unicode`]: crate::extensions::unicode::Unicode 60 /// 61 /// # Examples 62 /// 63 /// ``` 64 /// use icu::locale::{ 65 /// extensions::unicode::{subdivision_suffix, SubdivisionId}, 66 /// subtags::region, 67 /// }; 68 /// 69 /// let ss = subdivision_suffix!("zzzz"); 70 /// let region = region!("gb"); 71 /// 72 /// let si = SubdivisionId::new(region, ss); 73 /// 74 /// assert_eq!(si.to_string(), "gbzzzz"); 75 /// ``` 76 #[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Copy)] 77 #[non_exhaustive] 78 pub struct SubdivisionId { 79 /// A region field of a Subdivision Id. 80 pub region: Region, 81 /// A subdivision suffix field of a Subdivision Id. 82 pub suffix: SubdivisionSuffix, 83 } 84 85 impl SubdivisionId { 86 /// Returns a new [`SubdivisionId`]. 87 /// 88 /// # Examples 89 /// 90 /// ``` 91 /// use icu::locale::{ 92 /// extensions::unicode::{subdivision_suffix, SubdivisionId}, 93 /// subtags::region, 94 /// }; 95 /// 96 /// let ss = subdivision_suffix!("zzzz"); 97 /// let region = region!("gb"); 98 /// 99 /// let si = SubdivisionId::new(region, ss); 100 /// 101 /// assert_eq!(si.to_string(), "gbzzzz"); 102 /// ``` new(region: Region, suffix: SubdivisionSuffix) -> Self103 pub const fn new(region: Region, suffix: SubdivisionSuffix) -> Self { 104 Self { region, suffix } 105 } 106 107 /// A constructor which takes a str slice, parses it and 108 /// produces a well-formed [`SubdivisionId`]. 109 #[inline] try_from_str(s: &str) -> Result<Self, ParseError>110 pub fn try_from_str(s: &str) -> Result<Self, ParseError> { 111 Self::try_from_utf8(s.as_bytes()) 112 } 113 114 /// See [`Self::try_from_str`] try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError>115 pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> { 116 let is_alpha = code_units 117 .first() 118 .and_then(|b| { 119 b.is_ascii_alphabetic() 120 .then_some(true) 121 .or_else(|| b.is_ascii_digit().then_some(false)) 122 }) 123 .ok_or(ParseError::InvalidExtension)?; 124 let region_len = if is_alpha { 2 } else { 3 }; 125 let (region_code_units, suffix_code_units) = code_units 126 .split_at_checked(region_len) 127 .ok_or(ParseError::InvalidExtension)?; 128 let region = 129 Region::try_from_utf8(region_code_units).map_err(|_| ParseError::InvalidExtension)?; 130 let suffix = SubdivisionSuffix::try_from_utf8(suffix_code_units)?; 131 Ok(Self { region, suffix }) 132 } 133 134 /// Convert to [`Subtag`] into_subtag(self) -> Subtag135 pub fn into_subtag(self) -> Subtag { 136 let result = self.region.to_tinystr().concat(self.suffix.to_tinystr()); 137 Subtag::from_tinystr_unvalidated(result) 138 } 139 } 140 141 impl writeable::Writeable for SubdivisionId { 142 #[inline] write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result143 fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result { 144 sink.write_str(self.region.to_tinystr().to_ascii_lowercase().as_str())?; 145 sink.write_str(self.suffix.as_str()) 146 } 147 148 #[inline] writeable_length_hint(&self) -> writeable::LengthHint149 fn writeable_length_hint(&self) -> writeable::LengthHint { 150 self.region.writeable_length_hint() + self.suffix.writeable_length_hint() 151 } 152 } 153 154 writeable::impl_display_with_writeable!(SubdivisionId); 155 156 impl FromStr for SubdivisionId { 157 type Err = ParseError; 158 159 #[inline] from_str(s: &str) -> Result<Self, Self::Err>160 fn from_str(s: &str) -> Result<Self, Self::Err> { 161 Self::try_from_str(s) 162 } 163 } 164 165 #[cfg(test)] 166 mod tests { 167 use super::*; 168 169 #[test] test_subdivisionid_fromstr()170 fn test_subdivisionid_fromstr() { 171 let si: SubdivisionId = "gbzzzz".parse().expect("Failed to parse SubdivisionId"); 172 assert_eq!(si.region.to_string(), "GB"); 173 assert_eq!(si.suffix.to_string(), "zzzz"); 174 assert_eq!(si.to_string(), "gbzzzz"); 175 176 for sample in ["", "gb", "o"] { 177 let oe: Result<SubdivisionId, _> = sample.parse(); 178 assert!(oe.is_err(), "Should fail: {}", sample); 179 } 180 } 181 } 182