Skip to main content

opencc_rust/
lib.rs

1/*!
2Open Chinese Convert(OpenCC, 開放中文轉換) binding for the Rust language for conversion between Traditional Chinese and Simplified Chinese.
3
4## Compilation
5
6To compile this crate, you need to compile the OpenCC C++ library first. You can install OpenCC in your operating system, or in somewhere in your file system. As for the latter, you need to set the following environment variables to link the OpenCC library:
7
8* `OPENCC_LIB_DIRS`: The directories of library files, like `-L`. Use `:` to separate.
9* `OPENCC_LIBS`: The library names that you want to link, like `-l`. Use `:` to separate. Typically, it contains **opencc:marisa**.
10* `OPENCC_INCLUDE_DIRS`: The directories of header files, like `-i`. Use `:` to separate.
11* `OPENCC_STATIC`: Whether to use `static` or `dylib`.
12* `OPENCC_DYLIB_STDCPP`: If you use `static` linking, and your OpenCC library is compiled by the GNU C, this environment variable should be set.
13* `OPENCC_STATIC_STDCPP`: If you use `static` linking, and your OpenCC library is compiled by musl libc, this environment variable should be set.
14
15## Examples
16
17```rust
18use opencc_rust::*;
19
20let opencc = OpenCC::new(DefaultConfig::TW2SP).unwrap();
21
22let s = opencc.convert("涼風有訊").unwrap();
23
24assert_eq!("凉风有讯", &s);
25
26let s = opencc.convert_to_buffer(",秋月無邊", s).unwrap();
27
28assert_eq!("凉风有讯,秋月无边", &s);
29```
30
31```rust
32use opencc_rust::*;
33
34let opencc = OpenCC::new(DefaultConfig::S2TWP).unwrap();
35
36let s = opencc.convert("凉风有讯").unwrap();
37
38assert_eq!("涼風有訊", &s);
39
40let s = opencc.convert_to_buffer(",秋月无边", s).unwrap();
41
42assert_eq!("涼風有訊,秋月無邊", &s);
43```
44
45## Static Dictionaries
46
47Usually, OpenCC needs to be executed on an environment where OpenCC is installed. If you want to make it portable, you can enable the `static-dictionaries` feature.
48
49```toml
50[dependencies.opencc-rust]
51version = "*"
52features = ["static-dictionaries"]
53```
54Then, the `generate_static_dictionary` and `generate_static_dictionaries` functions are available.
55
56The default OpenCC dictionaries will be compiled into the binary file by `lazy_static_include` crate. And you can use the two functions to recover them on demand.
57
58For example,
59
60```rust,ignore
61use opencc_rust::*;
62
63let output_path = "/path/to/dictionaries-directory";
64
65generate_static_dictionary(&output_path, DefaultConfig::TW2SP).unwrap();
66
67let opencc = OpenCC::new(Path::join(&output_path, DefaultConfig::TW2SP)).unwrap();
68
69assert_eq!("凉风有讯", &opencc.convert("涼風有訊").unwrap());
70```
71*/
72
73#![cfg_attr(docsrs, feature(doc_cfg))]
74
75#[cfg(feature = "static-dictionaries")]
76#[macro_use]
77extern crate lazy_static_include;
78
79#[cfg(feature = "static-dictionaries")]
80use std::fs::{self, File};
81#[cfg(feature = "static-dictionaries")]
82use std::io::Write;
83#[cfg(feature = "static-dictionaries")]
84use std::sync::LazyLock;
85use std::{
86    ffi::{CStr, CString},
87    fmt,
88    path::Path,
89    sync::Mutex,
90};
91
92use enum_ordinalize::Ordinalize;
93use libc::{c_char, c_int, c_void, size_t};
94
95#[link(name = "opencc")]
96unsafe extern "C" {
97    pub fn opencc_open(config_file_path: *const c_char) -> *mut c_void;
98    pub fn opencc_close(opencc: *mut c_void) -> c_int;
99    pub fn opencc_convert_utf8(
100        opencc: *mut c_void,
101        input: *const c_char,
102        length: size_t,
103    ) -> *mut c_char;
104    pub fn opencc_convert_utf8_to_buffer(
105        opencc: *mut c_void,
106        input: *const c_char,
107        length: size_t,
108        output: *mut c_char,
109    ) -> size_t;
110    pub fn opencc_convert_utf8_free(str: *mut c_char);
111    pub fn opencc_error() -> *const c_char;
112}
113
114#[cfg(feature = "static-dictionaries")]
115struct SD(&'static str, &'static [u8]);
116
117#[cfg(feature = "static-dictionaries")]
118macro_rules! new_sd_instance {
119    ($name:ident, $file_name:expr) => {
120        static $name: LazyLock<SD> = LazyLock::new(|| {
121            lazy_static_include_bytes! {
122                RES => ("opencc", $file_name)
123            }
124
125            SD($file_name, &RES)
126        });
127    };
128}
129
130#[cfg(feature = "static-dictionaries")]
131new_sd_instance!(CJK_COMPATIBILITY_IDEOGRAPHS_OCD, "CJK_Compatibility_Ideographs.ocd2");
132#[cfg(feature = "static-dictionaries")]
133new_sd_instance!(HK2S_JSON, "hk2s.json");
134#[cfg(feature = "static-dictionaries")]
135new_sd_instance!(HK2SP_JSON, "hk2sp.json");
136#[cfg(feature = "static-dictionaries")]
137new_sd_instance!(HK2T_JSON, "hk2t.json");
138#[cfg(feature = "static-dictionaries")]
139new_sd_instance!(HKPHRASES_OCD, "HKPhrases.ocd2");
140#[cfg(feature = "static-dictionaries")]
141new_sd_instance!(HKPHRASES_REV_OCD, "HKPhrasesRev.ocd2");
142#[cfg(feature = "static-dictionaries")]
143new_sd_instance!(HKVARIANTS_OCD, "HKVariants.ocd2");
144#[cfg(feature = "static-dictionaries")]
145new_sd_instance!(HKVARIANTS_PHRASES_OCD, "HKVariantsPhrases.ocd2");
146#[cfg(feature = "static-dictionaries")]
147new_sd_instance!(HKVARIANTS_REV_OCD, "HKVariantsRev.ocd2");
148#[cfg(feature = "static-dictionaries")]
149new_sd_instance!(HKVARIANTS_REV_PHRASES_OCD, "HKVariantsRevPhrases.ocd2");
150#[cfg(feature = "static-dictionaries")]
151new_sd_instance!(JP2T_JSON, "jp2t.json");
152#[cfg(feature = "static-dictionaries")]
153new_sd_instance!(JPSHINJITAI_CHARACTERS_OCD, "JPShinjitaiCharacters.ocd2");
154#[cfg(feature = "static-dictionaries")]
155new_sd_instance!(JPSHINJITAI_CHARACTERS_REV_OCD, "JPShinjitaiCharactersRev.ocd2");
156#[cfg(feature = "static-dictionaries")]
157new_sd_instance!(JPSHINJITAI_PHRASES_OCD, "JPShinjitaiPhrases.ocd2");
158#[cfg(feature = "static-dictionaries")]
159new_sd_instance!(S2HK_JSON, "s2hk.json");
160#[cfg(feature = "static-dictionaries")]
161new_sd_instance!(S2HKP_JSON, "s2hkp.json");
162#[cfg(feature = "static-dictionaries")]
163new_sd_instance!(S2T_JSON, "s2t.json");
164#[cfg(feature = "static-dictionaries")]
165new_sd_instance!(S2TW_JSON, "s2tw.json");
166#[cfg(feature = "static-dictionaries")]
167new_sd_instance!(S2TWP_JSON, "s2twp.json");
168#[cfg(feature = "static-dictionaries")]
169new_sd_instance!(STCHARACTERS_OCD, "STCharacters.ocd2");
170#[cfg(feature = "static-dictionaries")]
171new_sd_instance!(
172    STPHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD,
173    "STPhrases_GeneratedFromRegionalPhrases.ocd2"
174);
175#[cfg(feature = "static-dictionaries")]
176new_sd_instance!(STPHRASES_OCD, "STPhrases.ocd2");
177#[cfg(feature = "static-dictionaries")]
178new_sd_instance!(T2HK_JSON, "t2hk.json");
179#[cfg(feature = "static-dictionaries")]
180new_sd_instance!(T2JP_JSON, "t2jp.json");
181#[cfg(feature = "static-dictionaries")]
182new_sd_instance!(T2S_JSON, "t2s.json");
183#[cfg(feature = "static-dictionaries")]
184new_sd_instance!(T2TW_JSON, "t2tw.json");
185#[cfg(feature = "static-dictionaries")]
186new_sd_instance!(TSCHARACTERS_OCD, "TSCharacters.ocd2");
187#[cfg(feature = "static-dictionaries")]
188new_sd_instance!(TSCHARACTERS_EXT_OCD, "TSCharactersExt.ocd2");
189#[cfg(feature = "static-dictionaries")]
190new_sd_instance!(TSPHRASES_OCD, "TSPhrases.ocd2");
191#[cfg(feature = "static-dictionaries")]
192new_sd_instance!(TW2S_JSON, "tw2s.json");
193#[cfg(feature = "static-dictionaries")]
194new_sd_instance!(TW2SP_JSON, "tw2sp.json");
195#[cfg(feature = "static-dictionaries")]
196new_sd_instance!(TW2T_JSON, "tw2t.json");
197#[cfg(feature = "static-dictionaries")]
198new_sd_instance!(TWPHRASES_OCD, "TWPhrases.ocd2");
199#[cfg(feature = "static-dictionaries")]
200new_sd_instance!(TWPHRASES_REV_OCD, "TWPhrasesRev.ocd2");
201#[cfg(feature = "static-dictionaries")]
202new_sd_instance!(TWVARIANTS_OCD, "TWVariants.ocd2");
203#[cfg(feature = "static-dictionaries")]
204new_sd_instance!(TWVARIANTS_PHRASES_OCD, "TWVariantsPhrases.ocd2");
205#[cfg(feature = "static-dictionaries")]
206new_sd_instance!(TWVARIANTS_REV_OCD, "TWVariantsRev.ocd2");
207#[cfg(feature = "static-dictionaries")]
208new_sd_instance!(TWVARIANTS_REV_PHRASES_OCD, "TWVariantsRevPhrases.ocd2");
209
210/// Default configs.
211#[derive(Debug, Copy, Clone, Ordinalize)]
212pub enum DefaultConfig {
213    /// Traditional Chinese (Hong Kong variant) to Simplified Chinese
214    HK2S,
215    /// Traditional Chinese (Hong Kong variant) to Simplified Chinese (with phrases)
216    HK2SP,
217    /// Traditional Chinese (Hong Kong variant) to Traditional Chinese (OpenCC Standard)
218    HK2T,
219    /// New Japanese Kanji (Shinjitai) to Old Japanese Kanji (Kyūjitai)
220    JP2T,
221    /// Simplified Chinese to Traditional Chinese (Hong Kong variant)
222    S2HK,
223    /// Simplified Chinese to Traditional Chinese (Hong Kong variant, with phrases)
224    S2HKP,
225    /// Simplified Chinese to Traditional Chinese (OpenCC Standard)
226    S2T,
227    /// Simplified Chinese to Traditional Chinese (Taiwan Standard)
228    S2TW,
229    /// Simplified Chinese to Traditional Chinese (Taiwan Standard, with Taiwan Phrases)
230    S2TWP,
231    /// Traditional Chinese (OpenCC Standard) to Traditional Chinese (Hong Kong variant)
232    T2HK,
233    /// Old Japanese Kanji (Kyūjitai) to New Japanese Kanji (Shinjitai)
234    T2JP,
235    /// Traditional Chinese (OpenCC Standard) to Simplified Chinese
236    T2S,
237    /// Traditional Chinese (OpenCC Standard) to Traditional Chinese (Taiwan Standard)
238    T2TW,
239    /// Traditional Chinese (Taiwan Standard) to Simplified Chinese
240    TW2S,
241    /// Traditional Chinese (Taiwan Standard) to Simplified Chinese (Mainland China Phrases)
242    TW2SP,
243    /// Traditional Chinese (Taiwan Standard) to Traditional Chinese (OpenCC Standard)
244    TW2T,
245}
246
247impl DefaultConfig {
248    /// Get the file name for this default config.
249    pub fn get_file_name(self) -> &'static str {
250        match self {
251            DefaultConfig::HK2S => "hk2s.json",
252            DefaultConfig::HK2SP => "hk2sp.json",
253            DefaultConfig::HK2T => "hk2t.json",
254            DefaultConfig::JP2T => "jp2t.json",
255            DefaultConfig::S2HK => "s2hk.json",
256            DefaultConfig::S2HKP => "s2hkp.json",
257            DefaultConfig::S2T => "s2t.json",
258            DefaultConfig::S2TW => "s2tw.json",
259            DefaultConfig::S2TWP => "s2twp.json",
260            DefaultConfig::T2HK => "t2hk.json",
261            DefaultConfig::T2JP => "t2jp.json",
262            DefaultConfig::T2S => "t2s.json",
263            DefaultConfig::T2TW => "t2tw.json",
264            DefaultConfig::TW2S => "tw2s.json",
265            DefaultConfig::TW2SP => "tw2sp.json",
266            DefaultConfig::TW2T => "tw2t.json",
267        }
268    }
269}
270
271impl AsRef<Path> for DefaultConfig {
272    fn as_ref(&self) -> &Path {
273        Path::new(self.get_file_name())
274    }
275}
276
277impl AsRef<str> for DefaultConfig {
278    fn as_ref(&self) -> &str {
279        self.get_file_name()
280    }
281}
282
283/// OpenCC binding for Rust.
284pub struct OpenCC {
285    opencc: *mut c_void,
286}
287
288/// Errors returned by the OpenCC wrapper.
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub enum OpenCCError {
291    /// The config file path is not valid UTF-8.
292    InvalidConfigPath,
293    /// The config file path contains a null byte.
294    ConfigPathContainsNul,
295    /// The input string contains a null byte.
296    InputContainsNul,
297    /// OpenCC reported an error through its C API.
298    OpenCC(String),
299    /// OpenCC returned bytes that are not valid UTF-8.
300    InvalidOutput,
301}
302
303impl fmt::Display for OpenCCError {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        match self {
306            OpenCCError::InvalidConfigPath => {
307                f.write_str("The config file path is not valid UTF-8.")
308            },
309            OpenCCError::ConfigPathContainsNul => {
310                f.write_str("The config file path contains a null byte.")
311            },
312            OpenCCError::InputContainsNul => f.write_str("The input string contains a null byte."),
313            OpenCCError::OpenCC(message) => f.write_str(message),
314            OpenCCError::InvalidOutput => {
315                f.write_str("OpenCC returned bytes that are not valid UTF-8.")
316            },
317        }
318    }
319}
320
321impl std::error::Error for OpenCCError {}
322
323static OPENCC_ERROR_LOCK: Mutex<()> = Mutex::new(());
324
325fn opencc_error_lock() -> std::sync::MutexGuard<'static, ()> {
326    OPENCC_ERROR_LOCK.lock().unwrap_or_else(|error| error.into_inner())
327}
328
329fn last_opencc_error() -> OpenCCError {
330    let error = unsafe { opencc_error() };
331
332    if error.is_null() {
333        return OpenCCError::OpenCC("OpenCC returned an unknown error.".to_string());
334    }
335
336    match unsafe { CStr::from_ptr(error) }.to_str() {
337        Ok(message) if !message.is_empty() => OpenCCError::OpenCC(message.to_string()),
338        _ => OpenCCError::OpenCC("OpenCC returned an unreadable error message.".to_string()),
339    }
340}
341
342// SAFETY: OpenCC handles can be moved between threads, and the non-thread-safe error API is serialized.
343unsafe impl Send for OpenCC {}
344
345// SAFETY: OpenCC conversion calls take an immutable handle, and the non-thread-safe error API is serialized.
346unsafe impl Sync for OpenCC {}
347
348impl OpenCC {
349    /// Create a new OpenCC instance through a file provided by its path.
350    pub fn new<P: AsRef<Path>>(config_file_path: P) -> Result<OpenCC, OpenCCError> {
351        let config_file_path =
352            config_file_path.as_ref().as_os_str().to_str().ok_or(OpenCCError::InvalidConfigPath)?;
353        let config_file_path =
354            CString::new(config_file_path).map_err(|_| OpenCCError::ConfigPathContainsNul)?;
355
356        // OpenCC stores the last error globally, so the failing call and error read share one lock.
357        let opencc = {
358            let _lock = opencc_error_lock();
359            let opencc = unsafe { opencc_open(config_file_path.as_ptr()) };
360
361            if opencc.is_null() || opencc as usize == usize::MAX {
362                return Err(last_opencc_error());
363            }
364
365            opencc
366        };
367
368        Ok(OpenCC {
369            opencc,
370        })
371    }
372
373    /// Convert a string to another string.
374    pub fn convert<S: AsRef<str>>(&self, input: S) -> Result<String, OpenCCError> {
375        let input = input.as_ref();
376
377        let length = input.len();
378        let input = CString::new(input).map_err(|_| OpenCCError::InputContainsNul)?;
379
380        let result_ptr = {
381            let _lock = opencc_error_lock();
382            let result_ptr = unsafe { opencc_convert_utf8(self.opencc, input.as_ptr(), length) };
383
384            if result_ptr.is_null() {
385                return Err(last_opencc_error());
386            }
387
388            result_ptr
389        };
390
391        let result_cstr = unsafe { CStr::from_ptr(result_ptr) };
392        let result =
393            result_cstr.to_str().map(str::to_string).map_err(|_| OpenCCError::InvalidOutput);
394
395        unsafe {
396            opencc_convert_utf8_free(result_ptr);
397        }
398
399        result
400    }
401
402    /// Convert a string to another string and store into a buffer.
403    pub fn convert_to_buffer<S: AsRef<str>>(
404        &self,
405        input: S,
406        mut output: String,
407    ) -> Result<String, OpenCCError> {
408        output.push_str(&self.convert(input)?);
409
410        Ok(output)
411    }
412}
413
414impl Drop for OpenCC {
415    fn drop(&mut self) {
416        if !self.opencc.is_null() {
417            unsafe {
418                opencc_close(self.opencc);
419            }
420        }
421    }
422}
423
424#[cfg(feature = "static-dictionaries")]
425fn generate_static_dictionary_inner<P: AsRef<Path>>(
426    path: P,
427    config: DefaultConfig,
428) -> Result<(), &'static str> {
429    let path = path.as_ref();
430
431    let mut output_data: Vec<&SD> = Vec::new();
432
433    // Each config JSON must be generated with every OCD2 dictionary file that it references.
434    match config {
435        DefaultConfig::HK2S => {
436            output_data.push(&HK2S_JSON);
437            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
438            output_data.push(&TSPHRASES_OCD);
439            output_data.push(&HKVARIANTS_REV_PHRASES_OCD);
440            output_data.push(&HKVARIANTS_REV_OCD);
441            output_data.push(&TSPHRASES_OCD);
442            output_data.push(&TSCHARACTERS_EXT_OCD);
443            output_data.push(&TSCHARACTERS_OCD);
444        },
445        DefaultConfig::HK2SP => {
446            output_data.push(&HK2SP_JSON);
447            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
448            output_data.push(&TSPHRASES_OCD);
449            output_data.push(&HKPHRASES_REV_OCD);
450            output_data.push(&HKVARIANTS_REV_PHRASES_OCD);
451            output_data.push(&HKVARIANTS_REV_OCD);
452            output_data.push(&TSPHRASES_OCD);
453            output_data.push(&TSCHARACTERS_EXT_OCD);
454            output_data.push(&TSCHARACTERS_OCD);
455        },
456        DefaultConfig::HK2T => {
457            output_data.push(&HK2T_JSON);
458            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
459            output_data.push(&HKVARIANTS_REV_PHRASES_OCD);
460            output_data.push(&HKVARIANTS_REV_OCD);
461        },
462        DefaultConfig::JP2T => {
463            output_data.push(&JP2T_JSON);
464            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
465            output_data.push(&JPSHINJITAI_PHRASES_OCD);
466            output_data.push(&JPSHINJITAI_CHARACTERS_OCD);
467        },
468        DefaultConfig::S2HK => {
469            output_data.push(&S2HK_JSON);
470            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
471            output_data.push(&STPHRASES_OCD);
472            output_data.push(&STPHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD);
473            output_data.push(&STCHARACTERS_OCD);
474            output_data.push(&HKVARIANTS_PHRASES_OCD);
475            output_data.push(&HKVARIANTS_OCD);
476        },
477        DefaultConfig::S2HKP => {
478            output_data.push(&S2HKP_JSON);
479            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
480            output_data.push(&STPHRASES_OCD);
481            output_data.push(&STPHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD);
482            output_data.push(&STCHARACTERS_OCD);
483            output_data.push(&HKPHRASES_OCD);
484            output_data.push(&HKVARIANTS_PHRASES_OCD);
485            output_data.push(&HKVARIANTS_OCD);
486        },
487        DefaultConfig::S2T => {
488            output_data.push(&S2T_JSON);
489            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
490            output_data.push(&STPHRASES_OCD);
491            output_data.push(&STPHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD);
492            output_data.push(&STCHARACTERS_OCD);
493        },
494        DefaultConfig::S2TW => {
495            output_data.push(&S2TW_JSON);
496            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
497            output_data.push(&STPHRASES_OCD);
498            output_data.push(&STPHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD);
499            output_data.push(&STCHARACTERS_OCD);
500            output_data.push(&TWVARIANTS_PHRASES_OCD);
501            output_data.push(&TWVARIANTS_OCD);
502        },
503        DefaultConfig::S2TWP => {
504            output_data.push(&S2TWP_JSON);
505            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
506            output_data.push(&STPHRASES_OCD);
507            output_data.push(&STPHRASES_GENERATED_FROM_REGIONAL_PHRASES_OCD);
508            output_data.push(&STCHARACTERS_OCD);
509            output_data.push(&TWPHRASES_OCD);
510            output_data.push(&TWVARIANTS_PHRASES_OCD);
511            output_data.push(&TWVARIANTS_OCD);
512        },
513        DefaultConfig::T2HK => {
514            output_data.push(&T2HK_JSON);
515            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
516            output_data.push(&HKVARIANTS_PHRASES_OCD);
517            output_data.push(&HKVARIANTS_OCD);
518        },
519        DefaultConfig::T2JP => {
520            output_data.push(&T2JP_JSON);
521            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
522            output_data.push(&JPSHINJITAI_CHARACTERS_REV_OCD);
523        },
524        DefaultConfig::T2S => {
525            output_data.push(&T2S_JSON);
526            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
527            output_data.push(&TSPHRASES_OCD);
528            output_data.push(&TSCHARACTERS_EXT_OCD);
529            output_data.push(&TSCHARACTERS_OCD);
530        },
531        DefaultConfig::T2TW => {
532            output_data.push(&T2TW_JSON);
533            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
534            output_data.push(&TWVARIANTS_PHRASES_OCD);
535            output_data.push(&TWVARIANTS_OCD);
536        },
537        DefaultConfig::TW2S => {
538            output_data.push(&TW2S_JSON);
539            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
540            output_data.push(&TSPHRASES_OCD);
541            output_data.push(&TWVARIANTS_REV_PHRASES_OCD);
542            output_data.push(&TWVARIANTS_REV_OCD);
543            output_data.push(&TSPHRASES_OCD);
544            output_data.push(&TSCHARACTERS_EXT_OCD);
545            output_data.push(&TSCHARACTERS_OCD);
546        },
547        DefaultConfig::TW2SP => {
548            output_data.push(&TW2SP_JSON);
549            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
550            output_data.push(&TSPHRASES_OCD);
551            output_data.push(&TWPHRASES_REV_OCD);
552            output_data.push(&TWVARIANTS_REV_PHRASES_OCD);
553            output_data.push(&TWVARIANTS_REV_OCD);
554            output_data.push(&TSPHRASES_OCD);
555            output_data.push(&TSCHARACTERS_EXT_OCD);
556            output_data.push(&TSCHARACTERS_OCD);
557        },
558        DefaultConfig::TW2T => {
559            output_data.push(&TW2T_JSON);
560            output_data.push(&CJK_COMPATIBILITY_IDEOGRAPHS_OCD);
561            output_data.push(&TWVARIANTS_REV_PHRASES_OCD);
562            output_data.push(&TWVARIANTS_REV_OCD);
563        },
564    }
565
566    for data in output_data {
567        let output_path = path.join(data.0);
568
569        if output_path.exists() {
570            if output_path.is_file() {
571                continue;
572            } else {
573                return Err("The dictionary is not correct.");
574            }
575        }
576
577        let mut file = File::create(output_path).map_err(|_| "Cannot create a new file.")?;
578
579        file.write(data.1).map_err(|_| "Cannot write data to a file.")?;
580
581        file.flush().map_err(|_| "Cannot flush file.")?;
582    }
583
584    Ok(())
585}
586
587#[cfg(feature = "static-dictionaries")]
588/// Generate files for a specific dictionary. These files are used for opening a new OpenCC instance.
589pub fn generate_static_dictionary<P: AsRef<Path>>(
590    path: P,
591    config: DefaultConfig,
592) -> Result<(), &'static str> {
593    let path = path.as_ref();
594
595    if path.exists() {
596        if !path.is_dir() {
597            return Err("The path of static dictionaries needs to be a directory.");
598        }
599    } else {
600        match fs::create_dir_all(path) {
601            Ok(_) => (),
602            Err(_) => return Err("Cannot create new directories."),
603        }
604    }
605
606    generate_static_dictionary_inner(path, config)
607}
608
609#[cfg(feature = "static-dictionaries")]
610/// Generate files for specific dictionaries. These files are used for opening a new OpenCC instance.
611pub fn generate_static_dictionaries<P: AsRef<Path>>(
612    path: P,
613    configs: &[DefaultConfig],
614) -> Result<(), &'static str> {
615    let path = path.as_ref();
616
617    if path.exists() {
618        if !path.is_dir() {
619            return Err("The path of static dictionaries needs to be a directory.");
620        }
621    } else {
622        match fs::create_dir_all(path) {
623            Ok(_) => (),
624            Err(_) => return Err("Cannot create new directories."),
625        }
626    }
627
628    for config in configs.iter().copied() {
629        generate_static_dictionary_inner(path, config)?
630    }
631
632    Ok(())
633}