ferrous_opencc/dictionary/
mod.rs

1//! 负责词典处理的模块
2
3mod dict_group;
4mod fst_dict;
5
6pub(super) mod embedded {
7    include!(concat!(env!("OUT_DIR"), "/embedded_map.rs"));
8}
9
10use crate::config::DictConfig;
11use crate::error::{OpenCCError, Result};
12use dict_group::DictGroup;
13pub use fst_dict::FstDict;
14use std::fmt::Debug;
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17
18/// 代表词典基本功能的 trait
19pub(super) trait Dictionary: Send + Sync + Debug {
20    /// 在词典中查找给定单词的最长前缀匹配
21    ///
22    /// # 返回
23    ///
24    /// 如果找到匹配,返回一个包含 `(匹配到的键, 匹配到的值列表)` 的元组
25    fn match_prefix<'a>(&self, word: &'a str) -> Option<(&'a str, Vec<String>)>;
26
27    /// 返回词典中的最长键长度,可用于分词算法的优化
28    fn max_key_length(&self) -> usize;
29}
30
31/// 一个内部枚举,用作词典工厂函数的命名空间
32/// 它用于根据配置分发不同词典的加载逻辑。
33#[allow(dead_code)]
34pub(super) enum DictType {
35    Fst(FstDict),
36    Group(DictGroup),
37}
38
39impl DictType {
40    /// 从文件加载字典
41    pub(super) fn from_config(
42        config: &DictConfig,
43        config_dir: &Path,
44    ) -> Result<Arc<dyn Dictionary>> {
45        match config.dict_type.as_str() {
46            "text" | "ocd2" => {
47                let file_name = config.file.as_ref().ok_or_else(|| {
48                    OpenCCError::InvalidConfig("'file' not found for 'text' dict".to_string())
49                })?;
50                let dict_path = find_dict_path(file_name, config_dir)?;
51                let dict = FstDict::new(&dict_path)?;
52                Ok(Arc::new(dict))
53            }
54            "group" => {
55                let dict_configs = config.dicts.as_ref().ok_or_else(|| {
56                    OpenCCError::InvalidConfig("'dicts' not found for 'group' dict".to_string())
57                })?;
58                let mut dicts = Vec::new();
59                for dict_config in dict_configs {
60                    // 递归调用 from_config 来构建子词典
61                    dicts.push(Self::from_config(dict_config, config_dir)?);
62                }
63                let dict_group = DictGroup::new(dicts);
64                Ok(Arc::new(dict_group))
65            }
66            _ => Err(OpenCCError::UnsupportedDictType(config.dict_type.clone())),
67        }
68    }
69
70    /// 从嵌入的资源加载字典
71    pub(super) fn from_config_embedded(config: &DictConfig) -> Result<Arc<dyn Dictionary>> {
72        match config.dict_type.as_str() {
73            "text" | "ocd2" => {
74                let file_name = config.file.as_ref().ok_or_else(|| {
75                    OpenCCError::InvalidConfig("'file' not found for 'text' dict".to_string())
76                })?;
77
78                // 只在嵌入式 map 中查找
79                let dict_bytes = embedded::EMBEDDED_DICTS
80                    .get(file_name.as_str())
81                    .ok_or_else(|| OpenCCError::ConfigNotFound(file_name.clone()))?;
82
83                let dict = FstDict::from_ocb_bytes(dict_bytes)?;
84                Ok(Arc::new(dict))
85            }
86            "group" => {
87                let dict_configs = config.dicts.as_ref().ok_or_else(|| {
88                    OpenCCError::InvalidConfig("'dicts' not found for 'group' dict".to_string())
89                })?;
90                let mut dicts = Vec::new();
91                for dict_config in dict_configs {
92                    // 递归调用嵌入式方法
93                    dicts.push(Self::from_config_embedded(dict_config)?);
94                }
95                let dict_group = DictGroup::new(dicts);
96                Ok(Arc::new(dict_group))
97            }
98            _ => Err(OpenCCError::UnsupportedDictType(config.dict_type.clone())),
99        }
100    }
101}
102
103/// 一个用于在配置目录中定位词典文件的辅助函数
104fn find_dict_path(file_name: &str, config_dir: &Path) -> Result<PathBuf> {
105    let path = config_dir.join(file_name);
106    if path.exists() {
107        return Ok(path);
108    }
109
110    Err(OpenCCError::FileNotFound(path.display().to_string()))
111}