lindera_dictionary/
builder.rs1pub mod character_definition;
2pub mod connection_cost_matrix;
3pub mod context_id_remap;
4pub mod metadata;
5pub mod prefix_dictionary;
6pub mod unknown_dictionary;
7pub mod user_dictionary;
8
9use std::fs;
10use std::path::Path;
11use std::sync::Arc;
12
13use csv::StringRecord;
14
15use self::character_definition::CharacterDefinitionBuilderOptions;
16use self::connection_cost_matrix::ConnectionCostMatrixBuilderOptions;
17use self::context_id_remap::compute_context_id_remap;
18use self::metadata::MetadataBuilder;
19use self::prefix_dictionary::PrefixDictionaryBuilderOptions;
20use self::unknown_dictionary::UnknownDictionaryBuilderOptions;
21use self::user_dictionary::{UserDictionaryBuilderOptions, build_user_dictionary};
22use crate::LinderaResult;
23use crate::dictionary::UserDictionary;
24use crate::dictionary::character_definition::CharacterDefinition;
25use crate::dictionary::context_id_map::ContextIdMap;
26use crate::dictionary::metadata::Metadata;
27use crate::error::LinderaErrorKind;
28
29#[derive(Clone)]
30pub struct DictionaryBuilder {
31 metadata: Metadata,
32 context_id_freq: Option<std::path::PathBuf>,
37}
38
39impl DictionaryBuilder {
40 pub fn new(metadata: Metadata) -> Self {
41 Self {
42 metadata,
43 context_id_freq: None,
44 }
45 }
46
47 pub fn with_context_id_freq(mut self, path: impl Into<std::path::PathBuf>) -> Self {
58 self.context_id_freq = Some(path.into());
59 self
60 }
61
62 pub fn build_dictionary(&self, input_dir: &Path, output_dir: &Path) -> LinderaResult<()> {
77 fs::create_dir_all(output_dir)
78 .map_err(|err| LinderaErrorKind::Io.with_error(anyhow::anyhow!(err)))?;
79
80 let remap: Option<Arc<ContextIdMap>> = if self.metadata.connection_id_mapping {
84 Some(Arc::new(compute_context_id_remap(
85 input_dir,
86 &self.metadata,
87 self.context_id_freq.as_deref(),
88 )?))
89 } else {
90 None
91 };
92
93 #[cfg(not(target_family = "wasm"))]
94 {
95 self.build_dictionary_parallel(input_dir, output_dir, remap.as_ref())
96 }
97 #[cfg(target_family = "wasm")]
98 {
99 self.build_dictionary_sequential(input_dir, output_dir, remap.as_ref())
100 }
101 }
102
103 #[cfg(target_family = "wasm")]
114 fn build_dictionary_sequential(
115 &self,
116 input_dir: &Path,
117 output_dir: &Path,
118 remap: Option<&Arc<ContextIdMap>>,
119 ) -> LinderaResult<()> {
120 self.build_metadata(output_dir, remap)?;
121 let chardef = self.build_character_definition(input_dir, output_dir)?;
122 self.build_unknown_dictionary(input_dir, output_dir, &chardef, remap.cloned())?;
123 self.build_prefix_dictionary(input_dir, output_dir, remap.cloned())?;
124 self.build_connection_cost_matrix(input_dir, output_dir, remap.cloned())?;
125
126 Ok(())
127 }
128
129 #[cfg(not(target_family = "wasm"))]
148 fn build_dictionary_parallel(
149 &self,
150 input_dir: &Path,
151 output_dir: &Path,
152 remap: Option<&Arc<ContextIdMap>>,
153 ) -> LinderaResult<()> {
154 std::thread::scope(|scope| {
155 let metadata = scope.spawn(move || self.build_metadata(output_dir, remap));
156 let unknown = scope.spawn(move || {
157 let chardef = self.build_character_definition(input_dir, output_dir)?;
158 self.build_unknown_dictionary(input_dir, output_dir, &chardef, remap.cloned())
159 });
160 let prefix = scope
161 .spawn(move || self.build_prefix_dictionary(input_dir, output_dir, remap.cloned()));
162 let matrix = scope.spawn(move || {
163 self.build_connection_cost_matrix(input_dir, output_dir, remap.cloned())
164 });
165
166 let results = [
168 metadata.join(),
169 unknown.join(),
170 prefix.join(),
171 matrix.join(),
172 ];
173 for result in results {
174 match result {
175 Ok(Ok(())) => {}
176 Ok(Err(err)) => return Err(err),
177 Err(panic) => std::panic::resume_unwind(panic),
178 }
179 }
180
181 Ok(())
182 })
183 }
184
185 pub fn build_metadata(
197 &self,
198 output_dir: &Path,
199 remap: Option<&Arc<ContextIdMap>>,
200 ) -> LinderaResult<()> {
201 match remap {
202 Some(map) => {
203 let mut metadata = self.metadata.clone();
204 metadata.context_id_map = Some(ContextIdMap::clone(map));
205 MetadataBuilder::new().build(&metadata, output_dir)
206 }
207 None => MetadataBuilder::new().build(&self.metadata, output_dir),
208 }
209 }
210
211 pub fn build_character_definition(
212 &self,
213 input_dir: &Path,
214 output_dir: &Path,
215 ) -> LinderaResult<CharacterDefinition> {
216 CharacterDefinitionBuilderOptions::default()
217 .encoding(self.metadata.encoding.clone())
218 .builder()
219 .build(input_dir, output_dir)
220 }
221
222 pub fn build_unknown_dictionary(
223 &self,
224 input_dir: &Path,
225 output_dir: &Path,
226 chardef: &CharacterDefinition,
227 remap: Option<Arc<ContextIdMap>>,
228 ) -> LinderaResult<()> {
229 UnknownDictionaryBuilderOptions::default()
230 .encoding(self.metadata.encoding.clone())
231 .context_id_remap(remap)
232 .builder()
233 .build(input_dir, chardef, output_dir)
234 }
235
236 pub fn build_prefix_dictionary(
237 &self,
238 input_dir: &Path,
239 output_dir: &Path,
240 remap: Option<Arc<ContextIdMap>>,
241 ) -> LinderaResult<()> {
242 PrefixDictionaryBuilderOptions::default()
243 .flexible_csv(self.metadata.flexible_csv)
244 .encoding(self.metadata.encoding.clone())
245 .skip_invalid_cost_or_id(self.metadata.skip_invalid_cost_or_id)
246 .normalize_details(self.metadata.normalize_details)
247 .schema(self.metadata.dictionary_schema.clone())
248 .context_id_remap(remap)
249 .builder()
250 .build(input_dir, output_dir)
251 }
252
253 pub fn build_connection_cost_matrix(
254 &self,
255 input_dir: &Path,
256 output_dir: &Path,
257 remap: Option<Arc<ContextIdMap>>,
258 ) -> LinderaResult<()> {
259 ConnectionCostMatrixBuilderOptions::default()
260 .encoding(self.metadata.encoding.clone())
261 .context_id_remap(remap)
262 .builder()
263 .build(input_dir, output_dir)
264 }
265
266 pub fn build_user_dictionary(
267 &self,
268 input_file: &Path,
269 output_file: &Path,
270 ) -> LinderaResult<()> {
271 let user_dict = self.build_user_dict(input_file)?;
272 build_user_dictionary(user_dict, output_file)
273 }
274
275 pub fn build_user_dict(&self, input_file: &Path) -> LinderaResult<UserDictionary> {
276 let userdic_schema = self.metadata.user_dictionary_schema.clone();
277 let dict_schema = self.metadata.dictionary_schema.clone();
278 let default_field_value = self.metadata.default_field_value.clone();
279
280 UserDictionaryBuilderOptions::default()
281 .user_dictionary_fields_num(self.metadata.user_dictionary_schema.field_count())
282 .dictionary_fields_num(self.metadata.dictionary_schema.field_count())
283 .default_word_cost(self.metadata.default_word_cost)
284 .default_left_context_id(self.metadata.default_left_context_id)
285 .default_right_context_id(self.metadata.default_right_context_id)
286 .flexible_csv(self.metadata.flexible_csv)
287 .user_dictionary_handler(Some(Box::new(move |row: &StringRecord| {
288 let mut result = Vec::new();
290
291 for field_name in dict_schema.get_custom_fields() {
293 if let Some(idx) = userdic_schema.get_field_index(field_name) {
294 if idx < row.len() {
296 result.push(row[idx].to_string());
297 } else {
298 result.push(default_field_value.clone());
299 }
300 } else {
301 result.push(default_field_value.clone());
303 }
304 }
305
306 Ok(result)
307 })))
308 .builder()
309 .build(input_file)
310 }
311}