Skip to main content

jpreprocess_dictionary/dictionary/to_dict/
mod.rs

1use std::{fs, path::Path};
2
3use lindera_dictionary::{
4    builder::{
5        character_definition::CharacterDefinitionBuilderOptions,
6        connection_cost_matrix::ConnectionCostMatrixBuilderOptions, metadata::MetadataBuilder,
7        unknown_dictionary::UnknownDictionaryBuilderOptions,
8        user_dictionary::build_user_dictionary,
9    },
10    dictionary::{
11        character_definition::CharacterDefinition, metadata::Metadata, schema::Schema,
12        UserDictionary,
13    },
14    error::LinderaErrorKind,
15    LinderaResult,
16};
17
18use crate::dictionary::to_dict::prefix_dictionary::{
19    generate_user_prefix_dictionary,
20    parser::{
21        DefaultParser, DefaultParserOptions, UserDictionaryParser, UserDictionaryParserOptions,
22    },
23    write_system_prefix_dictionary, CSVReaderOptions,
24};
25
26use super::word_encoding::JPreprocessDictionaryWordEncoding;
27
28mod prefix_dictionary;
29
30pub struct JPreprocessDictionaryBuilder {
31    metadata: Metadata,
32}
33
34impl JPreprocessDictionaryBuilder {
35    pub fn new(metadata: Metadata) -> Self {
36        Self { metadata }
37    }
38}
39
40impl Default for JPreprocessDictionaryBuilder {
41    fn default() -> Self {
42        Self {
43            metadata: Self::default_metadata(),
44        }
45    }
46}
47
48impl JPreprocessDictionaryBuilder {
49    pub fn default_metadata() -> Metadata {
50        Metadata {
51            dictionary_schema: Schema::new(vec![
52                "surface".to_string(),
53                "left_context_id".to_string(),
54                "right_context_id".to_string(),
55                "cost".to_string(),
56                "major_pos".to_string(),
57                "middle_pos".to_string(),
58                "small_pos".to_string(),
59                "fine_pos".to_string(),
60                "conjugation_type".to_string(),
61                "conjugation_form".to_string(),
62                "base_form".to_string(),
63                "reading".to_string(),
64                "pronunciation".to_string(),
65                // Additional fields
66                "accent_morasize".to_string(),
67                "chain_rule".to_string(),
68                "chain_flag".to_string(),
69            ]),
70            ..Default::default()
71        }
72    }
73
74    pub fn build_dictionary(&self, input_dir: &Path, output_dir: &Path) -> LinderaResult<()> {
75        fs::create_dir_all(output_dir)
76            .map_err(|err| LinderaErrorKind::Io.with_error(anyhow::anyhow!(err)))?;
77
78        self.build_metadata(output_dir)?;
79        let chardef = self.build_character_definition(input_dir, output_dir)?;
80        self.build_unknown_dictionary(input_dir, &chardef, output_dir)?;
81        self.build_prefix_dictionary(input_dir, output_dir)?;
82        self.build_connection_cost_matrix(input_dir, output_dir)?;
83
84        Ok(())
85    }
86
87    pub fn build_metadata(&self, output_dir: &Path) -> LinderaResult<()> {
88        MetadataBuilder::new().build(&self.metadata, output_dir)
89    }
90
91    pub fn build_user_dictionary(
92        &self,
93        input_file: &Path,
94        output_file: &Path,
95    ) -> LinderaResult<()> {
96        let user_dict = self.build_user_dict(input_file)?;
97        build_user_dictionary(user_dict, output_file)
98    }
99
100    pub fn build_character_definition(
101        &self,
102        input_dir: &Path,
103        output_dir: &Path,
104    ) -> LinderaResult<CharacterDefinition> {
105        CharacterDefinitionBuilderOptions::default()
106            .encoding(self.metadata.encoding.clone())
107            .builder()
108            .unwrap()
109            .build(input_dir, output_dir)
110    }
111
112    pub fn build_unknown_dictionary(
113        &self,
114        input_dir: &Path,
115        chardef: &CharacterDefinition,
116        output_dir: &Path,
117    ) -> LinderaResult<()> {
118        UnknownDictionaryBuilderOptions::default()
119            .encoding(self.metadata.encoding.clone())
120            .builder()
121            .unwrap()
122            .build(input_dir, chardef, output_dir)
123    }
124
125    pub fn build_prefix_dictionary(
126        &self,
127        input_dir: &Path,
128        output_dir: &Path,
129    ) -> LinderaResult<()> {
130        let reader = CSVReaderOptions::default()
131            .flexible_csv(self.metadata.flexible_csv)
132            .encoding(self.metadata.encoding.clone())
133            .normalize_details(self.metadata.normalize_details)
134            .builder()
135            .unwrap();
136        let rows = reader.load_csv_data(input_dir)?;
137
138        let parser = DefaultParserOptions::default()
139            .skip_invalid_cost_or_id(self.metadata.skip_invalid_cost_or_id)
140            .schema(self.metadata.dictionary_schema.clone())
141            .normalize_details(self.metadata.normalize_details)
142            .builder()
143            .unwrap();
144
145        write_system_prefix_dictionary::<DefaultParser, JPreprocessDictionaryWordEncoding>(
146            &parser, &rows, output_dir,
147        )
148    }
149
150    pub fn build_connection_cost_matrix(
151        &self,
152        input_dir: &Path,
153        output_dir: &Path,
154    ) -> LinderaResult<()> {
155        ConnectionCostMatrixBuilderOptions::default()
156            .encoding(self.metadata.encoding.clone())
157            .builder()
158            .unwrap()
159            .build(input_dir, output_dir)
160    }
161
162    pub fn build_user_dict(&self, input_file: &Path) -> LinderaResult<UserDictionary> {
163        let reader = CSVReaderOptions::default()
164            .flexible_csv(self.metadata.flexible_csv)
165            .builder()
166            .unwrap();
167        let rows = reader.read_csv_files(&[input_file.to_path_buf()])?;
168
169        self.build_user_dict_from_rows(rows)
170    }
171    pub fn build_user_dict_from_data(&self, data: Vec<Vec<&str>>) -> LinderaResult<UserDictionary> {
172        let rows = data
173            .into_iter()
174            .map(csv::StringRecord::from_iter)
175            .collect::<Vec<_>>();
176
177        self.build_user_dict_from_rows(rows)
178    }
179
180    fn build_user_dict_from_rows(
181        &self,
182        rows: Vec<csv::StringRecord>,
183    ) -> LinderaResult<UserDictionary> {
184        let parser = UserDictionaryParserOptions::default()
185            .user_dictionary_fields_num(self.metadata.user_dictionary_schema.field_count())
186            .default_word_cost(self.metadata.default_word_cost)
187            .default_left_context_id(self.metadata.default_left_context_id)
188            .default_right_context_id(self.metadata.default_right_context_id)
189            .dictionary_parser(
190                DefaultParserOptions::default()
191                    .skip_invalid_cost_or_id(self.metadata.skip_invalid_cost_or_id)
192                    .schema(self.metadata.dictionary_schema.clone())
193                    .normalize_details(self.metadata.normalize_details)
194                    .builder()
195                    .unwrap(),
196            )
197            .user_dictionary_parser(
198                DefaultParserOptions::default()
199                    .schema(self.metadata.user_dictionary_schema.clone())
200                    .normalize_details(self.metadata.normalize_details)
201                    .builder()
202                    .unwrap(),
203            )
204            .builder()
205            .unwrap();
206
207        let dict = generate_user_prefix_dictionary::<
208            UserDictionaryParser,
209            JPreprocessDictionaryWordEncoding,
210        >(&parser, &rows)?;
211
212        Ok(UserDictionary { dict })
213    }
214}
215
216#[deprecated(
217    note = "Use JPreprocessDictionaryBuilder::build_user_dict_from_data instead",
218    since = "0.13.0"
219)]
220pub fn build_user_dict_from_data(data: Vec<Vec<&str>>) -> LinderaResult<UserDictionary> {
221    let rows = data
222        .into_iter()
223        .map(csv::StringRecord::from_iter)
224        .collect::<Vec<_>>();
225
226    let builder = JPreprocessDictionaryBuilder::new(Metadata {
227        default_word_cost: -10000,
228        default_left_context_id: 0,
229        default_right_context_id: 0,
230        ..JPreprocessDictionaryBuilder::default_metadata()
231    });
232
233    builder.build_user_dict_from_rows(rows)
234}
235
236#[cfg(test)]
237mod tests {
238    use lindera_dictionary::viterbi::{LexType, WordEntry, WordId};
239
240    use super::*;
241
242    #[test]
243    fn test_user_dictionary() {
244        let builder = JPreprocessDictionaryBuilder::default();
245
246        let data = vec![
247            vec![
248                "東京スカイツリー",
249                "1285",
250                "1285",
251                "-3000",
252                "名詞",
253                "固有名詞",
254                "一般",
255                "*",
256                "*",
257                "*",
258                "*",
259                "トウキョウスカイツリー",
260                "トウキョウスカイツリー",
261                "13",
262                "*",
263                "*",
264            ],
265            vec![
266                "すもももももももものうち",
267                "1285",
268                "1285",
269                "-3000",
270                "名詞",
271                "固有名詞",
272                "一般",
273                "*",
274                "*",
275                "*",
276                "*",
277                "スモモモモモモモモノウチ",
278                "スモモモモモモモモノウチ",
279                "13",
280                "*",
281                "*",
282            ],
283        ];
284
285        let user_dict = builder.build_user_dict_from_data(data).unwrap();
286        assert_eq!(
287            user_dict.dict.find_surface("東京スカイツリー"),
288            vec![WordEntry {
289                word_id: WordId {
290                    id: 0,
291                    is_system: false,
292                    lex_type: LexType::User,
293                },
294                word_cost: -3000,
295                left_id: 1285,
296                right_id: 1285,
297            },]
298        );
299        assert_eq!(
300            user_dict.dict.find_surface("すもももももももものうち"),
301            vec![WordEntry {
302                word_id: WordId {
303                    id: 1,
304                    is_system: false,
305                    lex_type: LexType::User,
306                },
307                word_cost: -3000,
308                left_id: 1285,
309                right_id: 1285,
310            },]
311        );
312    }
313
314    #[test]
315    fn test_simple_user_dictionary() {
316        let builder = JPreprocessDictionaryBuilder::default();
317
318        let data = vec![
319            vec![
320                "東京スカイツリー",       // surface
321                "トウキョウスカイツリー", // reading
322                "トーキョースカイツリー", // pronunciation
323            ],
324            vec![
325                "すもももももももものうち",
326                "スモモモモモモモモノウチ",
327                "スモモモモモモモモノウチ",
328            ],
329        ];
330
331        let user_dict = builder.build_user_dict_from_data(data).unwrap();
332        assert_eq!(
333            user_dict.dict.find_surface("東京スカイツリー"),
334            vec![WordEntry {
335                word_id: WordId {
336                    id: 0,
337                    is_system: false,
338                    lex_type: LexType::User,
339                },
340                word_cost: -10000,
341                left_id: 1288,
342                right_id: 1288,
343            },]
344        );
345        assert_eq!(
346            user_dict.dict.find_surface("すもももももももものうち"),
347            vec![WordEntry {
348                word_id: WordId {
349                    id: 1,
350                    is_system: false,
351                    lex_type: LexType::User,
352                },
353                word_cost: -10000,
354                left_id: 1288,
355                right_id: 1288,
356            },]
357        );
358    }
359}