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::{DICTIONARY_FORMAT_VERSION, 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(
204 &self,
205 output_dir: &Path,
206 remap: Option<&Arc<ContextIdMap>>,
207 ) -> LinderaResult<()> {
208 let mut metadata = self.metadata.clone();
209 metadata.format_version = DICTIONARY_FORMAT_VERSION;
210 if let Some(map) = remap {
211 metadata.context_id_map = Some(ContextIdMap::clone(map));
212 }
213 MetadataBuilder::new().build(&metadata, output_dir)
214 }
215
216 pub fn build_character_definition(
217 &self,
218 input_dir: &Path,
219 output_dir: &Path,
220 ) -> LinderaResult<CharacterDefinition> {
221 CharacterDefinitionBuilderOptions::default()
222 .encoding(self.metadata.encoding.clone())
223 .builder()
224 .build(input_dir, output_dir)
225 }
226
227 pub fn build_unknown_dictionary(
228 &self,
229 input_dir: &Path,
230 output_dir: &Path,
231 chardef: &CharacterDefinition,
232 remap: Option<Arc<ContextIdMap>>,
233 ) -> LinderaResult<()> {
234 UnknownDictionaryBuilderOptions::default()
235 .encoding(self.metadata.encoding.clone())
236 .context_id_remap(remap)
237 .builder()
238 .build(input_dir, chardef, output_dir)
239 }
240
241 pub fn build_prefix_dictionary(
242 &self,
243 input_dir: &Path,
244 output_dir: &Path,
245 remap: Option<Arc<ContextIdMap>>,
246 ) -> LinderaResult<()> {
247 PrefixDictionaryBuilderOptions::default()
248 .flexible_csv(self.metadata.flexible_csv)
249 .encoding(self.metadata.encoding.clone())
250 .skip_invalid_cost_or_id(self.metadata.skip_invalid_cost_or_id)
251 .normalize_details(self.metadata.normalize_details)
252 .schema(self.metadata.dictionary_schema.clone())
253 .context_id_remap(remap)
254 .builder()
255 .build(input_dir, output_dir)
256 }
257
258 pub fn build_connection_cost_matrix(
259 &self,
260 input_dir: &Path,
261 output_dir: &Path,
262 remap: Option<Arc<ContextIdMap>>,
263 ) -> LinderaResult<()> {
264 ConnectionCostMatrixBuilderOptions::default()
265 .encoding(self.metadata.encoding.clone())
266 .context_id_remap(remap)
267 .builder()
268 .build(input_dir, output_dir)
269 }
270
271 pub fn build_user_dictionary(
272 &self,
273 input_file: &Path,
274 output_file: &Path,
275 ) -> LinderaResult<()> {
276 let user_dict = self.build_user_dict(input_file)?;
277 build_user_dictionary(user_dict, output_file)
278 }
279
280 pub fn build_user_dict(&self, input_file: &Path) -> LinderaResult<UserDictionary> {
281 let userdic_schema = self.metadata.user_dictionary_schema.clone();
282 let dict_schema = self.metadata.dictionary_schema.clone();
283 let default_field_value = self.metadata.default_field_value.clone();
284
285 UserDictionaryBuilderOptions::default()
286 .user_dictionary_fields_num(self.metadata.user_dictionary_schema.field_count())
287 .dictionary_fields_num(self.metadata.dictionary_schema.field_count())
288 .default_word_cost(self.metadata.default_word_cost)
289 .default_left_context_id(self.metadata.default_left_context_id)
290 .default_right_context_id(self.metadata.default_right_context_id)
291 .flexible_csv(self.metadata.flexible_csv)
292 .user_dictionary_handler(Some(Box::new(move |row: &StringRecord| {
293 let mut result = Vec::new();
295
296 for field_name in dict_schema.get_custom_fields() {
298 if let Some(idx) = userdic_schema.get_field_index(field_name) {
299 if idx < row.len() {
301 result.push(row[idx].to_string());
302 } else {
303 result.push(default_field_value.clone());
304 }
305 } else {
306 result.push(default_field_value.clone());
308 }
309 }
310
311 Ok(result)
312 })))
313 .builder()
314 .build(input_file)
315 }
316}