ferrous_opencc/dictionary/
mod.rs

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