jp_utils/reading/
mod.rs

1mod r_ref;
2pub mod traits;
3
4pub use r_ref::ReadingRef;
5
6use self::traits::AsReadingRef;
7
8#[cfg(feature = "furigana")]
9use crate::furi::segment::kanji::as_kanji::AsKanjiSegment;
10#[cfg(feature = "furigana")]
11use crate::furi::{parse::reading::FuriToReadingParser, Furigana};
12#[cfg(feature = "furigana")]
13use crate::furi::{segment::AsSegment, segment::Segment, seq::FuriSequence};
14
15/// Represents a Japanese 'reading' which always consists of a kana reading and sometimes an
16/// equivalent way to write that word with kanji. This is an owned variant. For a borrowed variant
17/// see [`ReadingRef`]
18#[derive(Debug, Clone, PartialEq, Eq, Default)]
19#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Reading {
21    kana: String,
22    kanji: Option<String>,
23}
24
25impl Reading {
26    /// Create a new kana reading.
27    #[inline]
28    pub fn new(kana: String) -> Self {
29        Self { kana, kanji: None }
30    }
31
32    /// Create a new reading with a kanji.
33    #[inline]
34    pub fn new_with_kanji(kana: String, kanji: String) -> Self {
35        Self {
36            kana,
37            kanji: Some(kanji),
38        }
39    }
40
41    /// Create a new reading where you can pass an `Option` for kanji.
42    #[inline]
43    pub fn new_raw(kana: String, kanji: Option<String>) -> Self {
44        Self { kana, kanji }
45    }
46
47    /// Returns `true` if the ReadingRef has a kanji reading.
48    #[inline]
49    pub fn has_kanji(&self) -> bool {
50        self.kanji.is_some()
51    }
52
53    /// Returns the kanji reading if available or uses kana as fallback.
54    #[inline]
55    pub fn kanji_or_kana(&self) -> &str {
56        self.kanji.as_deref().unwrap_or(&self.kana)
57    }
58
59    /// Returns the kanji reading if exists.
60    #[inline]
61    pub fn kanji(&self) -> Option<&str> {
62        self.kanji.as_deref()
63    }
64
65    /// Returns the readings kana reading
66    #[inline]
67    pub fn kana(&self) -> &str {
68        &self.kana
69    }
70
71    /// Converts the reading to a `(String, Option<String>)` tuple where the first String is the
72    /// kana reading and the `Option<String>` the kanji reading if available.
73    #[inline]
74    pub fn into_inner(self) -> (String, Option<String>) {
75        (self.kana, self.kanji)
76    }
77}
78
79impl AsReadingRef for Reading {
80    #[inline]
81    fn as_reading_ref(&self) -> ReadingRef {
82        ReadingRef::new_raw(&self.kana, self.kanji.as_deref())
83    }
84}
85
86impl PartialEq<ReadingRef<'_>> for Reading {
87    #[inline]
88    fn eq(&self, other: &ReadingRef) -> bool {
89        self.kana.as_str() == other.kana() && self.kanji.as_deref() == other.kanji()
90    }
91}
92
93#[cfg(feature = "furigana")]
94impl From<&FuriSequence<Segment>> for Reading {
95    #[inline]
96    fn from(value: &FuriSequence<Segment>) -> Self {
97        let kana = value.kana_reading().to_string();
98        let kanji = value.has_kanji().then(|| value.kanji_reading().to_string());
99        Self { kana, kanji }
100    }
101}
102
103#[cfg(feature = "furigana")]
104impl<T: AsRef<str>> From<&Furigana<T>> for Reading {
105    #[inline]
106    fn from(value: &Furigana<T>) -> Self {
107        let (kana, kanji) = FuriToReadingParser::parse_kanji_and_kana(value.raw());
108        Self::new_raw(kana, kanji)
109    }
110}
111
112#[cfg(feature = "furigana")]
113impl<T: AsRef<str>> From<Furigana<T>> for Reading {
114    #[inline]
115    fn from(value: Furigana<T>) -> Self {
116        let (kana, kanji) = FuriToReadingParser::parse_kanji_and_kana(value.raw());
117        Self::new_raw(kana, kanji)
118    }
119}
120
121#[cfg(feature = "furigana")]
122impl From<FuriSequence<Segment>> for Reading {
123    #[inline]
124    fn from(value: FuriSequence<Segment>) -> Self {
125        let kana = value.kana_reading().to_string();
126        let kanji = value.has_kanji().then(|| value.kanji_reading().to_string());
127        Self { kana, kanji }
128    }
129}
130
131#[cfg(feature = "furigana")]
132impl<S: AsSegment> FromIterator<S> for Reading {
133    fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Reading {
134        let mut kana = String::with_capacity(20);
135        let mut kanji = String::new();
136        let mut has_kanji = false;
137
138        for i in iter {
139            if let Some(k) = i.as_kanji() {
140                if !has_kanji {
141                    // lazy initialize kanji reading
142                    kanji = kana.clone();
143                    has_kanji = true;
144                }
145
146                let readings = k.readings();
147                if readings.is_empty() || readings[0].as_ref().is_empty() {
148                    kana.push_str(k.literals().as_ref());
149                    kanji.push_str(k.literals().as_ref());
150                    continue;
151                }
152
153                kanji.push_str(k.literals().as_ref());
154                for r in readings {
155                    kana.push_str(r.as_ref());
156                }
157            } else if let Some(k) = i.as_kana() {
158                let k = k.as_ref();
159                kana.push_str(k);
160                if has_kanji {
161                    kanji.push_str(k);
162                }
163            }
164        }
165        let kanji = has_kanji.then_some(kanji);
166        Reading::new_raw(kana, kanji)
167    }
168}
169
170#[cfg(feature = "furigana")]
171impl<A> From<A> for Reading
172where
173    A: AsSegment,
174{
175    #[inline]
176    fn from(value: A) -> Self {
177        let kana = value.get_kana_reading();
178        let kanji = value.as_kanji().map(|i| i.literals().as_ref().to_string());
179        Self { kana, kanji }
180    }
181}