Skip to main content

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}