kana/
kana.rs

1
2//! Converters of troublesome characters included in Japanese texts.
3//!
4//! * Half-width-kana[半角カナ;HANKAKU KANA] -> normal Katakana
5//! * Wide-alphanumeric[全角英数;ZENKAKU EISU] <-> normal ASCII
6//!
7//! # Example
8//! ```
9//! extern crate kana;
10//! use kana::*;
11//! 
12//! fn main() {
13//!     let s1 = "マツオ バショウ ア゚";
14//!     assert_eq!("マツオ バショウ ア ゚", half2kana(s1));
15//!     assert_eq!("マツオ バショウ ア゚", half2full(s1));
16//! 
17//!     let s2 = "ひ゜ひ゛んは゛";
18//!     assert_eq!("ぴびんば", combine(s2));
19//!     assert_eq!("ひ ゚ひ ゙んは ゙", vsmark2combi(s2));
20//! 
21//!     let s3 = "#&Rust-1.6!";
22//!     assert_eq!("#&Rust-1.6!", wide2ascii(s3));
23//! }
24//! ```
25
26#[macro_use] extern crate lazy_static;
27extern crate regex;
28
29use std::char;
30use std::collections::HashMap;
31use regex::Regex;
32
33//  0x3099  combining  ゙
34//  0x309A  combining  ゚
35//  0x309B  fullwidth ゛
36//  0x309C  fullwidth ゜
37//  0xFF9E  halfwidth ゙
38//  0xFF9F  halfwidth ゚
39//  0x20    space
40
41const CH_VOICED_COMBI:     char = '\u{3099}';
42const CH_SEMIVOICED_COMBI: char = '\u{309A}';
43const CH_VOICED_FULL:      char = '\u{309B}';
44const CH_SEMIVOICED_FULL:  char = '\u{309C}';
45const CH_VOICED_HALF:      char = '\u{FF9E}';
46const CH_SEMIVOICED_HALF:  char = '\u{FF9F}';
47const CH_SPACE:            char = '\u{20}';
48
49const VOICED_COMBI:          &'static str = "\u{3099}";
50const SEMIVOICED_COMBI:      &'static str = "\u{309A}";
51const VOICED_WITH_SPACE:     &'static str = "\u{20}\u{3099}";
52const SEMIVOICED_WITH_SPACE: &'static str = "\u{20}\u{309A}";
53
54const RE_VOICED_MARKS: &'static str
55    = r"(?:\x20??\x{3099}|\x{309B}|\x{FF9E})";
56const RE_SEMIVOICED_MARKS: &'static str
57    = r"(?:\x20??\x{309A}|\x{309C}|\x{FF9F})";
58
59lazy_static! {
60    static ref SEMIVOICED_HALVES: HashMap<char,char> = [
61        ('\u{FF8A}', '\u{30D1}'),   //  ハ	FF8A	パ	30D1
62        ('\u{FF8B}', '\u{30D4}'),   //  ヒ	FF8B	ピ	30D4
63        ('\u{FF8C}', '\u{30D7}'),   //  フ	FF8C	プ	30D7
64        ('\u{FF8D}', '\u{30DA}'),   //  ヘ	FF8D	ペ	30DA
65        ('\u{FF8E}', '\u{30DD}'),   //  ホ	FF8E	ポ	30DD
66    ].iter().copied().collect();
67
68    static ref VOICED_HALVES: HashMap<char,char> = [
69        ('\u{FF66}', '\u{30FA}'),   //  ヲ	FF66	ヺ	30FA
70        ('\u{FF73}', '\u{30F4}'),   //  ウ	FF73	ヴ	30F4
71        ('\u{FF76}', '\u{30AC}'),   //  カ	FF76	ガ	30AC
72        ('\u{FF77}', '\u{30AE}'),   //  キ	FF77	ギ	30AE
73        ('\u{FF78}', '\u{30B0}'),   //  ク	FF78	グ	30B0
74        ('\u{FF79}', '\u{30B2}'),   //  ケ	FF79	ゲ	30B2
75        ('\u{FF7A}', '\u{30B4}'),   //  コ	FF7A	ゴ	30B4
76        ('\u{FF7B}', '\u{30B6}'),   //  サ	FF7B	ザ	30B6
77        ('\u{FF7C}', '\u{30B8}'),   //  シ	FF7C	ジ	30B8
78        ('\u{FF7D}', '\u{30BA}'),   //  ス	FF7D	ズ	30BA
79        ('\u{FF7E}', '\u{30BC}'),   //  セ	FF7E	ゼ	30BC
80        ('\u{FF7F}', '\u{30BE}'),   //  ソ	FF7F	ゾ	30BE
81        ('\u{FF80}', '\u{30C0}'),   //  タ	FF80	ダ	30C0
82        ('\u{FF81}', '\u{30C2}'),   //  チ	FF81	ヂ	30C2
83        ('\u{FF82}', '\u{30C5}'),   //  ツ	FF82	ヅ	30C5
84        ('\u{FF83}', '\u{30C7}'),   //  テ	FF83	デ	30C7
85        ('\u{FF84}', '\u{30C9}'),   //  ト	FF84	ド	30C9
86        ('\u{FF8A}', '\u{30D0}'),   //  ハ	FF8A	バ	30D0
87        ('\u{FF8B}', '\u{30D3}'),   //  ヒ	FF8B	ビ	30D3
88        ('\u{FF8C}', '\u{30D6}'),   //  フ	FF8C	ブ	30D6
89        ('\u{FF8D}', '\u{30D9}'),   //  ヘ	FF8D	ベ	30D9
90        ('\u{FF8E}', '\u{30DC}'),   //  ホ	FF8E	ボ	30DC
91        ('\u{FF9C}', '\u{30F7}'),   //  ワ	FF9C	ヷ	30F7
92    ].iter().copied().collect();
93
94    static ref SEMIVOICES: HashMap<char,char> = [
95        ('\u{30CF}', '\u{30D1}'),   //  ハ	30CF	パ	30D1
96        ('\u{30D2}', '\u{30D4}'),   //  ヒ	30D2	ピ	30D4
97        ('\u{30D5}', '\u{30D7}'),   //  フ	30D5	プ	30D7
98        ('\u{30D8}', '\u{30DA}'),   //  ヘ	30D8	ペ	30DA
99        ('\u{30DB}', '\u{30DD}'),   //  ホ	30DB	ポ	30DD
100        ('\u{306F}', '\u{3071}'),   //  は	306F	ぱ	3071
101        ('\u{3072}', '\u{3074}'),   //  ひ	3072	ぴ	3074
102        ('\u{3075}', '\u{3077}'),   //  ふ	3075	ぷ	3077
103        ('\u{3078}', '\u{307A}'),   //  へ	3078	ぺ	307A
104        ('\u{307B}', '\u{307D}'),   //  ほ	307B	ぽ	307D
105    ].iter().copied().collect();
106
107    static ref VOICES: HashMap<char,char> = [
108        ('\u{30A6}', '\u{30F4}'),   //  ウ	30A6	ヴ	30F4
109        ('\u{30AB}', '\u{30AC}'),   //  カ	30AB	ガ	30AC
110        ('\u{30AD}', '\u{30AE}'),   //  キ	30AD	ギ	30AE
111        ('\u{30AF}', '\u{30B0}'),   //  ク	30AF	グ	30B0
112        ('\u{30B1}', '\u{30B2}'),   //  ケ	30B1	ゲ	30B2
113        ('\u{30B3}', '\u{30B4}'),   //  コ	30B3	ゴ	30B4
114        ('\u{30B5}', '\u{30B6}'),   //  サ	30B5	ザ	30B6
115        ('\u{30B7}', '\u{30B8}'),   //  シ	30B7	ジ	30B8
116        ('\u{30B9}', '\u{30BA}'),   //  ス	30B9	ズ	30BA
117        ('\u{30BB}', '\u{30BC}'),   //  セ	30BB	ゼ	30BC
118        ('\u{30BD}', '\u{30BE}'),   //  ソ	30BD	ゾ	30BE
119        ('\u{30BF}', '\u{30C0}'),   //  タ	30BF	ダ	30C0
120        ('\u{30C1}', '\u{30C2}'),   //  チ	30C1	ヂ	30C2
121        ('\u{30C4}', '\u{30C5}'),   //  ツ	30C4	ヅ	30C5
122        ('\u{30C6}', '\u{30C7}'),   //  テ	30C6	デ	30C7
123        ('\u{30C8}', '\u{30C9}'),   //  ト	30C8	ド	30C9
124        ('\u{30CF}', '\u{30D0}'),   //  ハ	30CF	バ	30D0
125        ('\u{30D2}', '\u{30D3}'),   //  ヒ	30D2	ビ	30D3
126        ('\u{30D5}', '\u{30D6}'),   //  フ	30D5	ブ	30D6
127        ('\u{30D8}', '\u{30D9}'),   //  ヘ	30D8	ベ	30D9
128        ('\u{30DB}', '\u{30DC}'),   //  ホ	30DB	ボ	30DC
129        ('\u{30EF}', '\u{30F7}'),   //  ワ	30EF	ヷ	30F7
130        ('\u{30F0}', '\u{30F8}'),   //  ヰ	30F0	ヸ	30F8
131        ('\u{30F1}', '\u{30F9}'),   //  ヱ	30F1	ヹ	30F9
132        ('\u{30F2}', '\u{30FA}'),   //  ヲ	30F2	ヺ	30FA
133        ('\u{3046}', '\u{3094}'),   //  う	3046	ゔ	3094
134        ('\u{304B}', '\u{304C}'),   //  か	304B	が	304C
135        ('\u{304D}', '\u{304E}'),   //  き	304D	ぎ	304E
136        ('\u{304F}', '\u{3050}'),   //  く	304F	ぐ	3050
137        ('\u{3051}', '\u{3052}'),   //  け	3051	げ	3052
138        ('\u{3053}', '\u{3054}'),   //  こ	3053	ご	3054
139        ('\u{3055}', '\u{3056}'),   //  さ	3055	ざ	3056
140        ('\u{3057}', '\u{3058}'),   //  し	3057	じ	3058
141        ('\u{3059}', '\u{305A}'),   //  す	3059	ず	305A
142        ('\u{305B}', '\u{305C}'),   //  せ	305B	ぜ	305C
143        ('\u{305D}', '\u{305E}'),   //  そ	305D	ぞ	305E
144        ('\u{305F}', '\u{3060}'),   //  た	305F	だ	3060
145        ('\u{3061}', '\u{3062}'),   //  ち	3061	ぢ	3062
146        ('\u{3064}', '\u{3065}'),   //  つ	3064	づ	3065
147        ('\u{3066}', '\u{3067}'),   //  て	3066	で	3067
148        ('\u{3068}', '\u{3069}'),   //  と	3068	ど	3069
149        ('\u{306F}', '\u{3070}'),   //  は	306F	ば	3070
150        ('\u{3072}', '\u{3073}'),   //  ひ	3072	び	3073
151        ('\u{3075}', '\u{3076}'),   //  ふ	3075	ぶ	3076
152        ('\u{3078}', '\u{3079}'),   //  へ	3078	べ	3079
153        ('\u{307B}', '\u{307C}'),   //  ほ	307B	ぼ	307C
154        ('\u{309D}', '\u{309E}'),   //  ゝ	309D	ゞ	309E
155    ].iter().copied().collect();
156
157    static ref HALVES: HashMap<char,char> = [
158        ('\u{FF61}', '\u{3002}'),   //  。	FF61	。	3002
159        ('\u{FF62}', '\u{300C}'),   //  「	FF62	「	300C
160        ('\u{FF63}', '\u{300D}'),   //  」	FF63	」	300D
161        ('\u{FF64}', '\u{3001}'),   //  、	FF64	、	3001
162        ('\u{FF65}', '\u{30FB}'),   //  ・	FF65	・	30FB
163        ('\u{FF66}', '\u{30F2}'),   //  ヲ	FF66	ヲ	30F2
164        ('\u{FF67}', '\u{30A1}'),   //  ァ	FF67	ァ	30A1
165        ('\u{FF68}', '\u{30A3}'),   //  ィ	FF68	ィ	30A3
166        ('\u{FF69}', '\u{30A5}'),   //  ゥ	FF69	ゥ	30A5
167        ('\u{FF6A}', '\u{30A7}'),   //  ェ	FF6A	ェ	30A7
168        ('\u{FF6B}', '\u{30A9}'),   //  ォ	FF6B	ォ	30A9
169        ('\u{FF6C}', '\u{30E3}'),   //  ャ	FF6C	ャ	30E3
170        ('\u{FF6D}', '\u{30E5}'),   //  ュ	FF6D	ュ	30E5
171        ('\u{FF6E}', '\u{30E7}'),   //  ョ	FF6E	ョ	30E7
172        ('\u{FF6F}', '\u{30C3}'),   //  ッ	FF6F	ッ	30C3
173        ('\u{FF70}', '\u{30FC}'),   //  ー	FF70	ー	30FC
174        ('\u{FF71}', '\u{30A2}'),   //  ア	FF71	ア	30A2
175        ('\u{FF72}', '\u{30A4}'),   //  イ	FF72	イ	30A4
176        ('\u{FF73}', '\u{30A6}'),   //  ウ	FF73	ウ	30A6
177        ('\u{FF74}', '\u{30A8}'),   //  エ	FF74	エ	30A8
178        ('\u{FF75}', '\u{30AA}'),   //  オ	FF75	オ	30AA
179        ('\u{FF76}', '\u{30AB}'),   //  カ	FF76	カ	30AB
180        ('\u{FF77}', '\u{30AD}'),   //  キ	FF77	キ	30AD
181        ('\u{FF78}', '\u{30AF}'),   //  ク	FF78	ク	30AF
182        ('\u{FF79}', '\u{30B1}'),   //  ケ	FF79	ケ	30B1
183        ('\u{FF7A}', '\u{30B3}'),   //  コ	FF7A	コ	30B3
184        ('\u{FF7B}', '\u{30B5}'),   //  サ	FF7B	サ	30B5
185        ('\u{FF7C}', '\u{30B7}'),   //  シ	FF7C	シ	30B7
186        ('\u{FF7D}', '\u{30B9}'),   //  ス	FF7D	ス	30B9
187        ('\u{FF7E}', '\u{30BB}'),   //  セ	FF7E	セ	30BB
188        ('\u{FF7F}', '\u{30BD}'),   //  ソ	FF7F	ソ	30BD
189        ('\u{FF80}', '\u{30BF}'),   //  タ	FF80	タ	30BF
190        ('\u{FF81}', '\u{30C1}'),   //  チ	FF81	チ	30C1
191        ('\u{FF82}', '\u{30C4}'),   //  ツ	FF82	ツ	30C4
192        ('\u{FF83}', '\u{30C6}'),   //  テ	FF83	テ	30C6
193        ('\u{FF84}', '\u{30C8}'),   //  ト	FF84	ト	30C8
194        ('\u{FF85}', '\u{30CA}'),   //  ナ	FF85	ナ	30CA
195        ('\u{FF86}', '\u{30CB}'),   //  ニ	FF86	ニ	30CB
196        ('\u{FF87}', '\u{30CC}'),   //  ヌ	FF87	ヌ	30CC
197        ('\u{FF88}', '\u{30CD}'),   //  ネ	FF88	ネ	30CD
198        ('\u{FF89}', '\u{30CE}'),   //  ノ	FF89	ノ	30CE
199        ('\u{FF8A}', '\u{30CF}'),   //  ハ	FF8A	ハ	30CF
200        ('\u{FF8B}', '\u{30D2}'),   //  ヒ	FF8B	ヒ	30D2
201        ('\u{FF8C}', '\u{30D5}'),   //  フ	FF8C	フ	30D5
202        ('\u{FF8D}', '\u{30D8}'),   //  ヘ	FF8D	ヘ	30D8
203        ('\u{FF8E}', '\u{30DB}'),   //  ホ	FF8E	ホ	30DB
204        ('\u{FF8F}', '\u{30DE}'),   //  マ	FF8F	マ	30DE
205        ('\u{FF90}', '\u{30DF}'),   //  ミ	FF90	ミ	30DF
206        ('\u{FF91}', '\u{30E0}'),   //  ム	FF91	ム	30E0
207        ('\u{FF92}', '\u{30E1}'),   //  メ	FF92	メ	30E1
208        ('\u{FF93}', '\u{30E2}'),   //  モ	FF93	モ	30E2
209        ('\u{FF94}', '\u{30E4}'),   //  ヤ	FF94	ヤ	30E4
210        ('\u{FF95}', '\u{30E6}'),   //  ユ	FF95	ユ	30E6
211        ('\u{FF96}', '\u{30E8}'),   //  ヨ	FF96	ヨ	30E8
212        ('\u{FF97}', '\u{30E9}'),   //  ラ	FF97	ラ	30E9
213        ('\u{FF98}', '\u{30EA}'),   //  リ	FF98	リ	30EA
214        ('\u{FF99}', '\u{30EB}'),   //  ル	FF99	ル	30EB
215        ('\u{FF9A}', '\u{30EC}'),   //  レ	FF9A	レ	30EC
216        ('\u{FF9B}', '\u{30ED}'),   //  ロ	FF9B	ロ	30ED
217        ('\u{FF9C}', '\u{30EF}'),   //  ワ	FF9C	ワ	30EF
218        ('\u{FF9D}', '\u{30F3}'),   //  ン	FF9D	ン	30F3
219        ('\u{FF9E}', '\u{3099}'),   //  ゙	FF9E	 ゙	3099
220        ('\u{FF9F}', '\u{309A}'),   //  ゚	FF9F	 ゚	309A
221        //('\u{FF9E}', '\u{309B}'),   //  ゙	FF9E	゛	309B
222        //('\u{FF9F}', '\u{309C}'),   //  ゚	FF9F	゜	309C
223    ].iter().copied().collect();
224}
225
226fn shift_code<F,G>(judge: F, convert: G, src: &str) -> String
227    where F: Fn(u32) -> bool,
228          G: Fn(u32) -> u32
229{
230    src.chars().map(|c| {
231        let k = c as u32;
232        if judge(k) { char::from_u32(convert(k)).unwrap() } else { c }
233    } ).collect()
234}
235
236/// Convert Wide-alphanumeric into normal ASCII  [A -> A]
237/// # Examples
238/// ```
239/// assert_eq!("#&Rust-1.6!", kana::wide2ascii("#&Rust-1.6!"));
240/// ```
241pub fn wide2ascii(s: &str) -> String {
242    shift_code(|x| 0xff00 < x && x < 0xff5f, |x| x - 0xfee0, s)
243}
244
245/// Convert normal ASCII characters into Wide-alphanumeric  [A -> A]
246/// # Examples
247/// ```
248/// assert_eq!("#&Rust-1.6!", kana::ascii2wide("#&Rust-1.6!"));
249/// ```
250pub fn ascii2wide(s: &str) -> String {
251    shift_code(|x| 0x0020 < x && x < 0x007f, |x| x + 0xfee0, s)
252}
253
254/// Convert Hiragana into Katakana  [あ -> ア]
255/// # Examples
256/// ```
257/// assert_eq!("イロハァィゥヴヵヶ", kana::hira2kata("いろはぁぃぅゔゕゖ"));
258/// ```
259pub fn hira2kata(s: &str) -> String {
260    shift_code(|x| 0x3041 <= x && x <= 0x3096, |x| x + 0x0060, s)
261}
262
263/// Convert Katakana into Hiragana  [ア -> あ]
264/// # Examples
265/// ```
266/// assert_eq!("いろはぁぃぅゔゕゖ", kana::kata2hira("イロハァィゥヴヵヶ"));
267/// ```
268pub fn kata2hira(s: &str) -> String {
269    shift_code(|x| 0x30A1 <= x && x <= 0x30F6, |x| x - 0x0060, s)
270}
271
272macro_rules! push_content {
273    ($judge:expr, $table:expr, $res:expr, $a:expr, $b:expr) => {
274        if $judge($b) {
275            if let Some(v) = $table.get(&$a) {
276                $res.push(*v);
277                return None;
278            }
279        }
280    };
281}
282
283/// Convert Half-width-kana into normal Katakana with diacritical marks separated  [ア゙パ -> ア゙パ]  
284///
285/// This method is simple, but tends to cause troubles when rendering.
286/// In such a case, use half2kana() or execute vsmark2{half|full|combi}() as a post process.
287/// # Examples
288/// ```
289/// assert_eq!("マツオ バショウ ア゚", kana::half2full("マツオ バショウ ア゚"));
290/// ```
291pub fn half2full(s: &str) -> String {
292    s.chars().map(|c| consult(&HALVES, &c)).collect()
293}
294
295/// Convert Half-width-kana into normal Katakana with diacritical marks combined  [ア゙パ -> ア゙パ]
296/// # Examples
297/// ```
298/// assert_eq!("マツオ バショウ ア ゚", kana::half2kana("マツオ バショウ ア゚"));
299/// ```
300pub fn half2kana(s: &str) -> String {
301    let mut line = String::with_capacity(s.len());
302    format!("{} ", s).chars().fold(None, |prev, b| {
303        if let Some(a) = prev {
304            push_content!(|b| b == CH_VOICED_HALF,
305                            VOICED_HALVES, line, a, b);
306            push_content!(|b| b == CH_SEMIVOICED_HALF,
307                            SEMIVOICED_HALVES, line, a, b);
308            if a == CH_VOICED_HALF ||
309                a == CH_SEMIVOICED_HALF { line.push(CH_SPACE); }
310            line.push(consult(&HALVES, &a));
311        }
312        Some(b)
313    } );
314
315    line
316}
317
318/// Combine base characters and diacritical marks on Hiragana/Katakana [がハ゜ -> がパ]
319/// # Examples
320/// ```
321/// assert_eq!("ぴびんば", kana::combine("ひ゜ひ゛んは゛"));
322/// ```
323pub fn combine(s: &str) -> String {
324    let ss = despace(s);
325    let mut line = String::with_capacity(ss.len());
326    format!("{} ", ss).chars().fold(None, |prev, b| {
327        if let Some(a) = prev {
328            push_content!(|b| b == CH_VOICED_HALF ||
329                                b == CH_VOICED_FULL ||
330                                b == CH_VOICED_COMBI,
331                            VOICES, line, a, b);
332            push_content!(|b| b == CH_SEMIVOICED_HALF ||
333                                b == CH_SEMIVOICED_FULL ||
334                                b == CH_SEMIVOICED_COMBI,
335                            SEMIVOICES, line, a, b);
336            line.push(a);
337        }
338        Some(b)
339    } );
340
341    enspace(&line)
342}
343
344fn consult(table: &HashMap<char,char>, c: &char) -> char {
345    match table.get(c) {
346        None    => *c,
347        Some(x) => *x,
348    }
349}
350
351fn despace(s: &str) -> String {
352    let s_ = &s.replace(VOICED_WITH_SPACE, VOICED_COMBI);
353    s_.replace(SEMIVOICED_WITH_SPACE, SEMIVOICED_COMBI)
354}
355
356fn enspace(s: &str) -> String {
357    let s_ = &s.replace(VOICED_COMBI, VOICED_WITH_SPACE);
358    s_.replace(SEMIVOICED_COMBI, SEMIVOICED_WITH_SPACE)
359}
360
361fn replace_marks(vmark: &str, svmark: &str, src: &str) -> String {
362    lazy_static! {
363        static ref RE1: Regex = Regex::new(RE_VOICED_MARKS).unwrap();
364        static ref RE2: Regex = Regex::new(RE_SEMIVOICED_MARKS).unwrap();
365    }
366    let s_ = RE1.replace_all(src, vmark);
367    RE2.replace_all(&s_, svmark)
368}
369
370/// Convert all separated Voiced-sound-marks into half-width style "\u{FF9E}"
371/// # Examples
372/// ```
373/// assert_eq!("ぴびんば", kana::vsmark2half("ぴひ゛んは ゙"));
374/// ```
375pub fn vsmark2half(s: &str) -> String {
376    replace_marks(&CH_VOICED_HALF.to_string(),
377                  &CH_SEMIVOICED_HALF.to_string(), s)
378}
379
380/// Convert all separated Voiced-sound-marks into full-width style "\u{309B}"
381/// # Examples
382/// ```
383/// assert_eq!("ひ゜ひ゛んは゛", kana::vsmark2full("ぴひ゛んは ゙"));
384/// ```
385pub fn vsmark2full(s: &str) -> String {
386    replace_marks(&CH_VOICED_FULL.to_string(),
387                  &CH_SEMIVOICED_FULL.to_string(), s)
388}
389
390/// Convert all separated Voiced-sound-marks into space+combining style "\u{20}\u{3099}"
391/// # Examples
392/// ```
393/// assert_eq!("ひ ゚ひ ゙んは ゙", kana::vsmark2combi("ぴひ゛んは ゙"));
394/// ```
395pub fn vsmark2combi(s: &str) -> String {
396    replace_marks(&VOICED_WITH_SPACE, &SEMIVOICED_WITH_SPACE, s)
397}
398
399/// Convert Wide-space into normal space    [" " -> " "]
400pub fn nowidespace(s: &str) -> String { s.replace("\u{3000}", "\u{20}") }
401
402/// Convert normal space into Wide-space    [" " -> " "]
403pub fn space2wide(s: &str) -> String { s.replace("\u{20}", "\u{3000}") }
404
405/// Convert Wide-yen into Half-width-yen    ["¥" -> "¥"]
406pub fn nowideyen(s: &str) -> String { s.replace("\u{ffe5}", "\u{a5}") }
407
408/// Convert Half-width-yen into Wide-yen    ["¥" -> "¥"]
409pub fn yen2wide(s: &str) -> String { s.replace("\u{a5}", "\u{ffe5}") }
410
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415
416    #[test]
417    fn pub_fn_t1() {
418        assert_eq!("!rust-0;", wide2ascii("!rust-0;"));
419        assert_eq!("!rust-0;", ascii2wide("!rust-0;"));
420        assert_eq!("カナ", hira2kata("かな"));
421        assert_eq!("かな", kata2hira("カナ"));
422    }
423
424    #[test]
425    fn pub_fn_t2() {
426        assert_eq!(" ", nowidespace(" "));
427        assert_eq!(" ", space2wide(" "));
428        assert_eq!("¥", nowideyen("¥"));
429        assert_eq!("¥", yen2wide("¥"));
430    }
431
432    #[test]
433    fn kana_t1() {
434        assert_eq!(Some(&'\u{30A2}'), HALVES.get(&'\u{FF71}'));
435        assert_eq!("ガナ", half2full("ガナ"));
436        assert_eq!("ガナ", half2kana("ガナ"));
437        assert_eq!("がな", combine("か゛な"));
438    }
439}