Skip to main content

opencc_rs/
lib.rs

1//! OpenCC bindings for Rust
2
3use std::ffi::{CStr, CString, c_void};
4use std::{fs, io};
5
6use libc::uintptr_t;
7use thiserror::Error;
8
9/// OpenCC bindings for Rust
10pub struct OpenCC {
11    openccs: Vec<*mut c_void>,
12}
13
14impl OpenCC {
15    /// Create a new OpenCC instance with the given configuration
16    pub fn new<T>(configs: T) -> Result<OpenCC, Error>
17    where
18        T: AsRef<[Config]>,
19    {
20        let configs = configs.as_ref();
21        assert!(!configs.is_empty());
22
23        let mut openccs = Vec::new();
24
25        for config in configs {
26            let config_data = config.get_data();
27            let dir = tempfile::tempdir()?;
28            for item in &config_data {
29                let file_path = dir.path().join(item.file_name);
30                fs::write(file_path, item.content)?;
31            }
32
33            let config_file_path = dir.path().join(config_data[0].file_name);
34            let config_file_path = CString::new(config_file_path.to_str().unwrap()).unwrap();
35
36            let opencc = unsafe { opencc_sys::opencc_open(config_file_path.as_ptr()) };
37
38            let ptr = opencc as uintptr_t;
39            if ptr == uintptr_t::MAX {
40                return Err(Error::Create);
41            }
42
43            openccs.push(opencc);
44        }
45
46        Ok(OpenCC { openccs })
47    }
48
49    /// Convert a string to another string
50    pub fn convert<T>(&self, input: T) -> Result<String, Error>
51    where
52        T: AsRef<str>,
53    {
54        let mut length = input.as_ref().len();
55        let input = CString::new(input.as_ref()).unwrap();
56        let mut result_ptr = input.as_ptr().cast_mut();
57
58        let mut free = Vec::new();
59
60        for opencc in &self.openccs {
61            result_ptr = unsafe { opencc_sys::opencc_convert_utf8(*opencc, result_ptr, length) };
62            if result_ptr.is_null() {
63                return Err(Error::Convert);
64            }
65
66            free.push(result_ptr);
67
68            if self.openccs.len() > 1 {
69                length = unsafe { libc::strlen(result_ptr) };
70            }
71        }
72
73        let result_cstr = unsafe { CStr::from_ptr(result_ptr) };
74        let result = unsafe { std::str::from_utf8_unchecked(result_cstr.to_bytes()).to_string() };
75
76        for ptr in free {
77            unsafe {
78                opencc_sys::opencc_convert_utf8_free(ptr);
79            }
80        }
81
82        Ok(result)
83    }
84}
85
86impl Drop for OpenCC {
87    fn drop(&mut self) {
88        for opencc in &self.openccs {
89            if !opencc.is_null() {
90                unsafe {
91                    opencc_sys::opencc_close(*opencc);
92                }
93            }
94        }
95    }
96}
97
98unsafe impl Send for OpenCC {}
99
100unsafe impl Sync for OpenCC {}
101
102/// Configurations
103pub enum Config {
104    /// Simplified Chinese to Traditional Chinese (OpenCC Standard)
105    S2T,
106    /// Traditional Chinese (OpenCC Standard) to Simplified Chinese
107    T2S,
108    /// Simplified Chinese to Traditional Chinese (Taiwan Standard)
109    S2TW,
110    /// Traditional Chinese (Taiwan Standard) to Simplified Chinese
111    TW2S,
112    /// Simplified Chinese to Traditional Chinese (Hong Kong variant)
113    S2HK,
114    /// Traditional Chinese (Hong Kong variant) to Simplified Chinese
115    HK2S,
116    /// Simplified Chinese to Traditional Chinese (Taiwan Standard, with Taiwan Phrases)
117    S2TWP,
118    /// Traditional Chinese (Taiwan Standard) to Simplified Chinese (Mainland China Phrases)
119    TW2SP,
120    /// Traditional Chinese (OpenCC Standard) to Traditional Chinese (Taiwan Standard)
121    T2TW,
122    /// Traditional Chinese (Taiwan Standard) to Traditional Chinese (OpenCC Standard)
123    TW2T,
124    /// Traditional Chinese (OpenCC Standard) to Traditional Chinese (Hong Kong variant)
125    T2HK,
126    /// Traditional Chinese (Hong Kong variant) to Traditional Chinese (OpenCC Standard)
127    HK2T,
128    /// Simplified Chinese to Traditional Chinese (Hong Kong variant, with Hong Kong Phrases)
129    S2HKP,
130    /// Traditional Chinese (Hong Kong variant) to Simplified Chinese (Mainland China Phrases)
131    HK2SP,
132    /// Old Japanese Kanji (Kyūjitai) to New Japanese Kanji (Shinjitai)
133    T2JP,
134    /// New Japanese Kanji (Shinjitai) to Old Japanese Kanji (Kyūjitai)
135    JP2T,
136}
137
138impl Config {
139    fn get_data(&self) -> Vec<&opencc_sys::Data> {
140        use opencc_sys::*;
141
142        match self {
143            Config::S2T => vec![
144                &S2T_JSON,
145                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
146                &ST_PHRASES_OCD2,
147                &ST_PHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD2,
148                &ST_CHARACTERS_OCD2,
149            ],
150            Config::T2S => vec![
151                &T2S_JSON,
152                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
153                &TS_PHRASES_OCD2,
154                &TS_CHARACTERS_EXT_OCD2,
155                &TS_CHARACTERS_OCD2,
156            ],
157            Config::S2TW => vec![
158                &S2TW_JSON,
159                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
160                &ST_PHRASES_OCD2,
161                &ST_PHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD2,
162                &ST_CHARACTERS_OCD2,
163                &TW_VARIANTS_PHRASES_OCD2,
164                &TW_VARIANTS_OCD2,
165            ],
166            Config::TW2S => vec![
167                &TW2S_JSON,
168                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
169                &TS_PHRASES_OCD2,
170                &TW_VARIANTS_REV_PHRASES_OCD2,
171                &TW_VARIANTS_REV_OCD2,
172                &TS_PHRASES_OCD2,
173                &TS_CHARACTERS_EXT_OCD2,
174                &TS_CHARACTERS_OCD2,
175            ],
176            Config::S2HK => vec![
177                &S2HK_JSON,
178                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
179                &ST_PHRASES_OCD2,
180                &ST_PHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD2,
181                &ST_CHARACTERS_OCD2,
182                &HK_VARIANTS_PHRASES_OCD2,
183                &HK_VARIANTS_OCD2,
184            ],
185            Config::HK2S => vec![
186                &HK2S_JSON,
187                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
188                &TS_PHRASES_OCD2,
189                &HK_VARIANTS_REV_PHRASES_OCD2,
190                &HK_VARIANTS_REV_OCD2,
191                &TS_PHRASES_OCD2,
192                &TS_CHARACTERS_EXT_OCD2,
193                &TS_CHARACTERS_OCD2,
194            ],
195            Config::S2TWP => vec![
196                &S2TWP_JSON,
197                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
198                &ST_PHRASES_OCD2,
199                &ST_PHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD2,
200                &ST_CHARACTERS_OCD2,
201                &TW_PHRASES_OCD2,
202                &TW_VARIANTS_PHRASES_OCD2,
203                &TW_VARIANTS_OCD2,
204            ],
205            Config::TW2SP => vec![
206                &TW2SP_JSON,
207                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
208                &TS_PHRASES_OCD2,
209                &TW_PHRASES_REV_OCD2,
210                &TW_VARIANTS_REV_PHRASES_OCD2,
211                &TW_VARIANTS_REV_OCD2,
212                &TS_PHRASES_OCD2,
213                &TS_CHARACTERS_EXT_OCD2,
214                &TS_CHARACTERS_OCD2,
215            ],
216            Config::T2TW => vec![
217                &T2TW_JSON,
218                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
219                &TW_VARIANTS_PHRASES_OCD2,
220                &TW_VARIANTS_OCD2,
221            ],
222            Config::TW2T => vec![
223                &TW2T_JSON,
224                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
225                &TW_VARIANTS_REV_PHRASES_OCD2,
226                &TW_VARIANTS_REV_OCD2,
227            ],
228            Config::T2HK => vec![
229                &T2HK_JSON,
230                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
231                &HK_VARIANTS_PHRASES_OCD2,
232                &HK_VARIANTS_OCD2,
233            ],
234            Config::HK2T => vec![
235                &HK2T_JSON,
236                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
237                &HK_VARIANTS_REV_PHRASES_OCD2,
238                &HK_VARIANTS_REV_OCD2,
239            ],
240            Config::S2HKP => vec![
241                &S2HKP_JSON,
242                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
243                &ST_PHRASES_OCD2,
244                &ST_PHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD2,
245                &ST_CHARACTERS_OCD2,
246                &HK_PHRASES_OCD2,
247                &HK_VARIANTS_PHRASES_OCD2,
248                &HK_VARIANTS_OCD2,
249            ],
250            Config::HK2SP => vec![
251                &HK2SP_JSON,
252                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
253                &TS_PHRASES_OCD2,
254                &HK_PHRASES_REV_OCD2,
255                &HK_VARIANTS_REV_PHRASES_OCD2,
256                &HK_VARIANTS_REV_OCD2,
257                &TS_PHRASES_OCD2,
258                &TS_CHARACTERS_EXT_OCD2,
259                &TS_CHARACTERS_OCD2,
260            ],
261            Config::T2JP => vec![
262                &T2JP_JSON,
263                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
264                &JP_SHINJITAI_CHARACTERS_REV_OCD2,
265            ],
266            Config::JP2T => vec![
267                &JP2T_JSON,
268                &CJK_COMPATIBILITY_IDEOGRAPHS_OCD2,
269                &JP_SHINJITAI_PHRASES_OCD2,
270                &JP_SHINJITAI_CHARATERS_OCD2,
271            ],
272        }
273    }
274}
275
276/// OpenCC error
277#[derive(Debug, Error)]
278pub enum Error {
279    /// Failed to create opencc instance
280    #[error("Failed to create opencc instance")]
281    Create,
282
283    /// Failed to convert the string
284    #[error("Failed to convert the string")]
285    Convert,
286
287    /// IO error
288    #[error(transparent)]
289    StdIO(#[from] io::Error),
290}