icu_normalizer/uts46.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//! Bundles the part of UTS 46 that makes sense to implement as a
6//! normalization.
7//!
8//! This is meant to be used as a building block of an UTS 46
9//! implementation, such as the `idna` crate.
10
11use crate::ComposingNormalizer;
12use crate::ComposingNormalizerBorrowed;
13use crate::IgnorableBehavior;
14use crate::IteratorPolicy;
15use crate::NormalizerNfcV2;
16use crate::NormalizerNfdTablesV1;
17use crate::NormalizerNfkdTablesV1;
18use crate::NormalizerUts46DataV1;
19use icu_collections::codepointtrie::CharIterWithTrie;
20use icu_collections::codepointtrie::CodePointTrie;
21use icu_provider::DataError;
22use icu_provider::DataProvider;
23
24type Trie46<'trie> = CodePointTrie<'trie, u32>;
25
26#[derive(Debug)]
27struct Uts46MapNormalizePolicy;
28
29impl IteratorPolicy for Uts46MapNormalizePolicy {
30 const IGNORABLE_BEHAVIOR: IgnorableBehavior = IgnorableBehavior::Ignored;
31}
32
33#[derive(Debug)]
34struct Uts46NormalizeValidatePolicy;
35
36impl IteratorPolicy for Uts46NormalizeValidatePolicy {
37 const IGNORABLE_BEHAVIOR: IgnorableBehavior = IgnorableBehavior::ReplacementCharacter;
38}
39
40// Implementation note: Despite merely wrapping a `ComposingNormalizer`,
41// having a `Uts46Mapper` serves two purposes:
42//
43// 1. Denying public access to parts of the `ComposingNormalizer` API
44// that don't work when the data contains markers for ignorables.
45// 2. Providing a place where additional iterator pre-processing or
46// post-processing can take place if needed in the future. (When
47// writing this, it looked like such processing was needed but
48// now isn't needed after all.)
49
50/// A borrowed version of a mapper that knows how to performs the
51/// subsets of UTS 46 processing documented on the methods.
52#[derive(Debug)]
53pub struct Uts46MapperBorrowed<'a> {
54 normalizer: ComposingNormalizerBorrowed<'a>,
55}
56
57#[cfg(feature = "compiled_data")]
58impl Default for Uts46MapperBorrowed<'static> {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl Uts46MapperBorrowed<'static> {
65 /// Cheaply converts a [`Uts46MapperBorrowed<'static>`] into a [`Uts46Mapper`].
66 ///
67 /// Note: Due to branching and indirection, using [`Uts46Mapper`] might inhibit some
68 /// compile-time optimizations that are possible with [`Uts46MapperBorrowed`].
69 pub const fn static_to_owned(self) -> Uts46Mapper {
70 Uts46Mapper {
71 normalizer: self.normalizer.static_to_owned(),
72 }
73 }
74
75 /// Construct with compiled data.
76 #[cfg(feature = "compiled_data")]
77 pub const fn new() -> Self {
78 Uts46MapperBorrowed {
79 normalizer: ComposingNormalizerBorrowed::new_uts46(),
80 }
81 }
82}
83
84impl Uts46MapperBorrowed<'_> {
85 /// Returns an iterator adaptor that turns an `Iterator` over `char`
86 /// into an iterator yielding a `char` sequence that gets the following
87 /// operations from the "Map" and "Normalize" steps of the "Processing"
88 /// section of UTS 46 lazily applied to it:
89 ///
90 /// 1. The `ignored` characters are ignored.
91 /// 2. The `mapped` characters are mapped.
92 /// 3. The `disallowed` characters are replaced with U+FFFD,
93 /// which itself is a disallowed character.
94 /// 4. The `deviation` characters are treated as `mapped` or `valid`
95 /// as appropriate.
96 /// 5. The `disallowed_STD3_valid` characters are treated as allowed.
97 /// 6. The `disallowed_STD3_mapped` characters are treated as
98 /// `mapped`.
99 /// 7. The result is normalized to NFC.
100 ///
101 /// Notably:
102 ///
103 /// * The STD3 or WHATWG ASCII deny list should be implemented as a
104 /// post-processing step.
105 /// * Transitional processing is not performed. Transitional mapping
106 /// would be a pre-processing step, but transitional processing is
107 /// deprecated, and none of Firefox, Safari, or Chrome use it.
108 pub fn map_normalize<'delegate, I: Iterator<Item = char> + 'delegate>(
109 &'delegate self,
110 iter: I,
111 ) -> impl Iterator<Item = char> + 'delegate {
112 let mut ret =
113 self.normalizer
114 .normalize_iter_private::<_, Trie46, Uts46MapNormalizePolicy>(
115 CharIterWithTrie::new(iter, self.normalizer.trie::<Trie46<'_>>()),
116 );
117 ret.decomposition.init(); // Discard the U+0000.
118 ret
119 }
120
121 /// Returns an iterator adaptor that turns an `Iterator` over `char`
122 /// into an iterator yielding a `char` sequence that gets the following
123 /// operations from the NFC check and statucs steps of the "Validity
124 /// Criteria" section of UTS 46 lazily applied to it:
125 ///
126 /// 1. The `ignored` characters are treated as `disallowed`.
127 /// 2. The `mapped` characters are mapped.
128 /// 3. The `disallowed` characters are replaced with U+FFFD,
129 /// which itself is a disallowed character.
130 /// 4. The `deviation` characters are treated as `mapped` or `valid`
131 /// as appropriate.
132 /// 5. The `disallowed_STD3_valid` characters are treated as allowed.
133 /// 6. The `disallowed_STD3_mapped` characters are treated as
134 /// `mapped`.
135 /// 7. The result is normalized to NFC.
136 ///
137 /// Notably:
138 ///
139 /// * The STD3 or WHATWG ASCII deny list should be implemented as a
140 /// post-processing step.
141 /// * Transitional processing is not performed. Transitional mapping
142 /// would be a pre-processing step, but transitional processing is
143 /// deprecated, and none of Firefox, Safari, or Chrome use it.
144 /// * The output needs to be compared with input to see if anything
145 /// changed. This check catches failures to adhere to the normalization
146 /// and status requirements. In particular, this comparison results
147 /// in _mapped_ characters resulting in error like "Validity Criteria"
148 /// requires.
149 #[inline]
150 pub fn normalize_validate<'delegate, I: Iterator<Item = char> + 'delegate>(
151 &'delegate self,
152 iter: I,
153 ) -> impl Iterator<Item = char> + 'delegate {
154 let mut ret = self
155 .normalizer
156 .normalize_iter_private::<_, Trie46, Uts46NormalizeValidatePolicy>(
157 CharIterWithTrie::new(iter, self.normalizer.trie::<Trie46<'_>>()),
158 );
159 ret.decomposition.init(); // Discard the U+0000.
160 ret
161 }
162}
163
164/// A mapper that knows how to performs the subsets of UTS 46 processing
165/// documented on the methods.
166#[derive(Debug)]
167pub struct Uts46Mapper {
168 normalizer: ComposingNormalizer,
169}
170
171#[cfg(feature = "compiled_data")]
172impl Default for Uts46Mapper {
173 fn default() -> Self {
174 Self::new().static_to_owned()
175 }
176}
177
178impl Uts46Mapper {
179 /// Constructs a borrowed version of this type for more efficient querying.
180 pub fn as_borrowed(&self) -> Uts46MapperBorrowed<'_> {
181 Uts46MapperBorrowed {
182 normalizer: self.normalizer.as_borrowed(),
183 }
184 }
185
186 /// Construct with compiled data.
187 #[cfg(feature = "compiled_data")]
188 #[expect(clippy::new_ret_no_self)]
189 pub const fn new() -> Uts46MapperBorrowed<'static> {
190 Uts46MapperBorrowed::new()
191 }
192
193 /// Construct with provider.
194 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
195 pub fn try_new<D>(provider: &D) -> Result<Self, DataError>
196 where
197 D: DataProvider<NormalizerUts46DataV1>
198 + DataProvider<NormalizerNfdTablesV1>
199 + DataProvider<NormalizerNfkdTablesV1>
200 // UTS 46 tables merged into NormalizerNfkdTablesV1
201 + DataProvider<NormalizerNfcV2>
202 + ?Sized,
203 {
204 let normalizer = ComposingNormalizer::try_new_uts46_unstable(provider)?;
205
206 Ok(Uts46Mapper { normalizer })
207 }
208}