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 "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 "東京スカイツリー", "トウキョウスカイツリー", "トーキョースカイツリー", ],
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}