Skip to main content

lindera_analysis/
token_filter.rs

1/// This module defines various token filters and provides functionality to load them.
2///
3/// # Modules
4/// - `japanese_base_form`: Contains the Japanese base form token filter.
5/// - `japanese_compound_word`: Contains the Japanese compound word token filter.
6/// - `japanese_kana`: Contains the Japanese kana token filter.
7/// - `japanese_katakana_stem`: Contains the Japanese katakana stem token filter.
8/// - `japanese_keep_tags`: Contains the Japanese keep tags token filter.
9/// - `japanese_number`: Contains the Japanese number token filter.
10/// - `japanese_reading_form`: Contains the Japanese reading form token filter.
11/// - `japanese_stop_tags`: Contains the Japanese stop tags token filter.
12/// - `keep_words`: Contains the keep words token filter.
13/// - `korean_keep_tags`: Contains the Korean keep tags token filter.
14/// - `korean_reading_form`: Contains the Korean reading form token filter.
15/// - `korean_stop_tags`: Contains the Korean stop tags token filter.
16/// - `length`: Contains the length token filter.
17/// - `lowercase`: Contains the lowercase token filter.
18/// - `mapping`: Contains the mapping token filter.
19/// - `remove_diacritical_mark`: Contains the remove diacritical mark token filter.
20/// - `stop_words`: Contains the stop words token filter.
21/// - `uppercase`: Contains the uppercase token filter.
22///
23/// # Traits
24/// - `TokenFilter`: A trait for token filters that can be applied to a vector of tokens.
25/// - `TokenFilterClone`: A trait for cloning boxed token filters.
26///
27/// # Structs
28/// - `BoxTokenFilter`: A boxed token filter that implements `TokenFilter`.
29/// - `TokenFilterLoader`: A loader for creating token filters from configuration values.
30///
31/// # Usage
32/// The `TokenFilterLoader` struct provides methods to load token filters from configuration values
33/// or command-line flags. The `TokenFilter` trait defines the interface for token filters, and
34/// `BoxTokenFilter` is a boxed implementation of a token filter.
35pub mod japanese_base_form;
36pub mod japanese_compound_word;
37pub mod japanese_kana;
38pub mod japanese_katakana_stem;
39pub mod japanese_keep_tags;
40pub mod japanese_number;
41pub mod japanese_reading_form;
42pub mod japanese_stop_tags;
43pub mod keep_words;
44pub mod korean_keep_tags;
45pub mod korean_reading_form;
46pub mod korean_stop_tags;
47pub mod length;
48pub mod lowercase;
49pub mod mapping;
50pub mod remove_diacritical_mark;
51pub mod stop_words;
52mod tags;
53pub mod uppercase;
54
55use serde_json::Value;
56use std::ops::Deref;
57
58use crate::parse_cli_flag;
59use crate::token_filter::japanese_base_form::{
60    JAPANESE_BASE_FORM_TOKEN_FILTER_NAME, JapaneseBaseFormTokenFilter,
61};
62use crate::token_filter::japanese_compound_word::{
63    JAPANESE_COMPOUND_WORD_TOKEN_FILTER_NAME, JapaneseCompoundWordTokenFilter,
64};
65use crate::token_filter::japanese_kana::{
66    JAPANESE_KANA_TOKEN_FILTER_NAME, JapaneseKanaTokenFilter,
67};
68use crate::token_filter::japanese_katakana_stem::{
69    JAPANESE_KATAKANA_STEM_TOKEN_FILTER_NAME, JapaneseKatakanaStemTokenFilter,
70};
71use crate::token_filter::japanese_keep_tags::{
72    JAPANESE_KEEP_TAGS_TOKEN_FILTER_NAME, JapaneseKeepTagsTokenFilter,
73};
74use crate::token_filter::japanese_number::{
75    JAPANESE_NUMBER_TOKEN_FILTER_NAME, JapaneseNumberTokenFilter,
76};
77use crate::token_filter::japanese_reading_form::{
78    JAPANESE_READING_FORM_TOKEN_FILTER_NAME, JapaneseReadingFormTokenFilter,
79};
80use crate::token_filter::japanese_stop_tags::{
81    JAPANESE_STOP_TAGS_TOKEN_FILTER_NAME, JapaneseStopTagsTokenFilter,
82};
83use crate::token_filter::keep_words::{KEEP_WORDS_TOKEN_FILTER_NAME, KeepWordsTokenFilter};
84use crate::token_filter::korean_keep_tags::{
85    KOREAN_KEEP_TAGS_TOKEN_FILTER_NAME, KoreanKeepTagsTokenFilter,
86};
87use crate::token_filter::korean_reading_form::{
88    KOREAN_READING_FORM_TOKEN_FILTER_NAME, KoreanReadingFormTokenFilter,
89};
90use crate::token_filter::korean_stop_tags::{
91    KOREAN_STOP_TAGS_TOKEN_FILTER_NAME, KoreanStopTagsTokenFilter,
92};
93use crate::token_filter::length::{LENGTH_TOKEN_FILTER_NAME, LengthTokenFilter};
94use crate::token_filter::lowercase::{LOWERCASE_TOKEN_FILTER_NAME, LowercaseTokenFilter};
95use crate::token_filter::mapping::{MAPPING_TOKEN_FILTER_NAME, MappingTokenFilter};
96use crate::token_filter::remove_diacritical_mark::{
97    REMOVE_DIACRITICAL_TOKEN_FILTER_NAME, RemoveDiacriticalMarkTokenFilter,
98};
99use crate::token_filter::stop_words::{STOP_WORDS_TOKEN_FILTER_NAME, StopWordsTokenFilter};
100use crate::token_filter::uppercase::{UPPERCASE_TOKEN_FILTER_NAME, UppercaseTokenFilter};
101use crate::{LinderaErrorKind, LinderaResult};
102use lindera::token::Token;
103
104/// A trait for token filters that can be applied to a vector of tokens.
105///
106/// This trait requires the implementor to be `'static`, `Send`, `Sync`, and
107/// implement the `TokenFilterClone` trait. It provides methods to get the
108/// name of the filter and to apply the filter to a mutable vector of tokens.
109///
110/// # Required Methods
111///
112/// - `name`: Returns the name of the token filter as a static string slice.
113/// - `apply`: Applies the token filter to a mutable vector of tokens, returning
114///   a `LinderaResult<()>`.
115pub trait TokenFilter: 'static + Send + Sync + TokenFilterClone {
116    fn name(&self) -> &'static str;
117    fn apply(&self, tokens: &mut Vec<Token<'_>>) -> LinderaResult<()>;
118}
119
120/// A `BoxTokenFilter` is a wrapper around a boxed trait object that implements
121/// the `TokenFilter` trait. This allows for dynamic dispatch of different
122/// `TokenFilter` implementations at runtime. The `BoxTokenFilter` ensures that
123/// the contained `TokenFilter` is thread-safe (`Send` and `Sync`) and has a
124/// static lifetime.
125pub struct BoxTokenFilter(Box<dyn TokenFilter + 'static + Send + Sync>);
126
127impl Deref for BoxTokenFilter {
128    type Target = dyn TokenFilter;
129
130    fn deref(&self) -> &dyn TokenFilter {
131        &*self.0
132    }
133}
134
135impl<T: TokenFilter> From<T> for BoxTokenFilter {
136    fn from(token_filter: T) -> BoxTokenFilter {
137        BoxTokenFilter(Box::new(token_filter))
138    }
139}
140
141/// A trait for cloning token filters.
142///
143/// This trait provides a method `box_clone` which allows for cloning
144/// a token filter and returning it as a boxed trait object.
145pub trait TokenFilterClone {
146    fn box_clone(&self) -> BoxTokenFilter;
147}
148
149impl<T: TokenFilter + Clone + 'static> TokenFilterClone for T {
150    fn box_clone(&self) -> BoxTokenFilter {
151        BoxTokenFilter::from(self.clone())
152    }
153}
154
155pub struct TokenFilterLoader {}
156
157impl TokenFilterLoader {
158    pub fn load_from_value(kind: &str, value: &Value) -> LinderaResult<BoxTokenFilter> {
159        // Creates a `BoxTokenFilter` based on the provided `kind` and `value`.
160        //
161        // The function matches the `kind` against various predefined token filter names
162        // and constructs the corresponding token filter using the configuration derived
163        // from `value`. If the `kind` does not match any of the predefined names, an error
164        // is returned.
165        //
166        // # Parameters
167        // - `kind`: A string slice that specifies the type of token filter to create.
168        // - `value`: A `serde_json::Value` that contains the configuration for the token filter.
169        //
170        // # Returns
171        // - `Result<BoxTokenFilter, LinderaError>`: A boxed token filter if the `kind` is recognized,
172        //   otherwise an error indicating that the token filter is unsupported.
173        //
174        // # Errors
175        // - Returns `LinderaErrorKind::Deserialize` if the `kind` is not supported or if there is an
176        //   error in creating the token filter from the provided `value`.
177        let token_filter = match kind {
178            JAPANESE_BASE_FORM_TOKEN_FILTER_NAME => {
179                BoxTokenFilter::from(JapaneseBaseFormTokenFilter::from_config(value)?)
180            }
181            JAPANESE_COMPOUND_WORD_TOKEN_FILTER_NAME => {
182                BoxTokenFilter::from(JapaneseCompoundWordTokenFilter::from_config(value)?)
183            }
184            JAPANESE_KANA_TOKEN_FILTER_NAME => {
185                BoxTokenFilter::from(JapaneseKanaTokenFilter::from_config(value)?)
186            }
187            JAPANESE_KATAKANA_STEM_TOKEN_FILTER_NAME => {
188                BoxTokenFilter::from(JapaneseKatakanaStemTokenFilter::from_config(value)?)
189            }
190            JAPANESE_KEEP_TAGS_TOKEN_FILTER_NAME => {
191                BoxTokenFilter::from(JapaneseKeepTagsTokenFilter::from_config(value)?)
192            }
193            JAPANESE_NUMBER_TOKEN_FILTER_NAME => {
194                BoxTokenFilter::from(JapaneseNumberTokenFilter::from_config(value)?)
195            }
196            JAPANESE_READING_FORM_TOKEN_FILTER_NAME => {
197                BoxTokenFilter::from(JapaneseReadingFormTokenFilter::from_config(value)?)
198            }
199            JAPANESE_STOP_TAGS_TOKEN_FILTER_NAME => {
200                BoxTokenFilter::from(JapaneseStopTagsTokenFilter::from_config(value)?)
201            }
202            KEEP_WORDS_TOKEN_FILTER_NAME => {
203                BoxTokenFilter::from(KeepWordsTokenFilter::from_config(value)?)
204            }
205            KOREAN_KEEP_TAGS_TOKEN_FILTER_NAME => {
206                BoxTokenFilter::from(KoreanKeepTagsTokenFilter::from_config(value)?)
207            }
208            KOREAN_READING_FORM_TOKEN_FILTER_NAME => {
209                BoxTokenFilter::from(KoreanReadingFormTokenFilter::from_config(value)?)
210            }
211            KOREAN_STOP_TAGS_TOKEN_FILTER_NAME => {
212                BoxTokenFilter::from(KoreanStopTagsTokenFilter::from_config(value)?)
213            }
214            LENGTH_TOKEN_FILTER_NAME => {
215                BoxTokenFilter::from(LengthTokenFilter::from_config(value)?)
216            }
217            LOWERCASE_TOKEN_FILTER_NAME => {
218                BoxTokenFilter::from(LowercaseTokenFilter::from_config(value)?)
219            }
220            MAPPING_TOKEN_FILTER_NAME => {
221                BoxTokenFilter::from(MappingTokenFilter::from_config(value)?)
222            }
223            REMOVE_DIACRITICAL_TOKEN_FILTER_NAME => {
224                BoxTokenFilter::from(RemoveDiacriticalMarkTokenFilter::from_config(value)?)
225            }
226            STOP_WORDS_TOKEN_FILTER_NAME => {
227                BoxTokenFilter::from(StopWordsTokenFilter::from_config(value)?)
228            }
229            UPPERCASE_TOKEN_FILTER_NAME => {
230                BoxTokenFilter::from(UppercaseTokenFilter::from_config(value)?)
231            }
232            _ => {
233                return Err(LinderaErrorKind::Deserialize
234                    .with_error(anyhow::anyhow!("unsupported token filter: {kind}")));
235            }
236        };
237
238        Ok(token_filter)
239    }
240
241    /// Loads a token filter based on a CLI flag string.
242    ///
243    /// # Arguments
244    ///
245    /// * `cli_flag` - A string slice representing the command-line interface (CLI) flag used to specify the token filter. The flag typically contains both the filter kind and its arguments.
246    ///
247    /// # Returns
248    ///
249    /// Returns a `LinderaResult<BoxTokenFilter>`, which is a boxed token filter, or an error if the CLI flag is invalid or the filter configuration cannot be loaded.
250    ///
251    /// # Process
252    ///
253    /// 1. **Parse CLI flag**:
254    ///    - The `parse_cli_flag` function is called to extract the filter kind and its arguments from the `cli_flag` string.
255    /// 2. **Load filter from parsed values**:
256    ///    - The filter kind and arguments are passed to `load_from_value`, which constructs the appropriate token filter based on the parsed values.
257    ///
258    /// # Errors
259    ///
260    /// - If the CLI flag cannot be parsed, an error is returned.
261    /// - If the filter kind or its configuration is invalid, an error is returned during the filter loading process.
262    ///
263    /// # Details
264    ///
265    /// - The CLI flag is parsed into a filter kind and arguments. These are then used to load the appropriate token filter using the `load_from_value` function.
266    pub fn load_from_cli_flag(cli_flag: &str) -> LinderaResult<BoxTokenFilter> {
267        let (kind, args) = parse_cli_flag(cli_flag)?;
268
269        let character_filter = Self::load_from_value(kind, &args)?;
270
271        Ok(character_filter)
272    }
273}