1use std::ffi::{CStr, CString, c_void};
4use std::{fs, io};
5
6use libc::uintptr_t;
7use thiserror::Error;
8
9pub struct OpenCC {
11 openccs: Vec<*mut c_void>,
12}
13
14impl OpenCC {
15 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 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
102pub enum Config {
104 S2T,
106 T2S,
108 S2TW,
110 TW2S,
112 S2HK,
114 HK2S,
116 S2TWP,
118 TW2SP,
120 T2TW,
122 TW2T,
124 T2HK,
126 HK2T,
128 S2HKP,
130 HK2SP,
132 T2JP,
134 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#[derive(Debug, Error)]
278pub enum Error {
279 #[error("Failed to create opencc instance")]
281 Create,
282
283 #[error("Failed to convert the string")]
285 Convert,
286
287 #[error(transparent)]
289 StdIO(#[from] io::Error),
290}