lindera_analysis/character_filter/
regex.rs1use regex::Regex;
2use serde_json::Value;
3
4use crate::character_filter::{CharacterFilter, OffsetMapping, Transformation};
5use lindera::LinderaResult;
6use lindera::error::LinderaErrorKind;
7
8pub const REGEX_CHARACTER_FILTER_NAME: &str = "regex";
9
10pub type RegexCharacterFilterConfig = Value;
11
12#[derive(Clone, Debug)]
15pub struct RegexCharacterFilter {
16 replacement: String,
17 regex: Regex,
18}
19
20impl RegexCharacterFilter {
21 pub fn new(pattern: &str, replacement: &str) -> LinderaResult<Self> {
22 let regex = Regex::new(pattern).map_err(|err| LinderaErrorKind::Args.with_error(err))?;
23
24 Ok(Self {
25 replacement: replacement.to_string(),
26 regex,
27 })
28 }
29
30 pub fn from_config(config: &RegexCharacterFilterConfig) -> LinderaResult<Self> {
31 let pattern = config
32 .get("pattern")
33 .ok_or_else(|| {
34 LinderaErrorKind::Deserialize.with_error(anyhow::anyhow!("missing pattern config."))
35 })?
36 .as_str()
37 .ok_or_else(|| {
38 LinderaErrorKind::Deserialize.with_error(anyhow::anyhow!("invalid pattern config."))
39 })?;
40
41 let replacement = config
42 .get("replacement")
43 .ok_or_else(|| {
44 LinderaErrorKind::Deserialize
45 .with_error(anyhow::anyhow!("missing replacement config."))
46 })?
47 .as_str()
48 .ok_or_else(|| {
49 LinderaErrorKind::Deserialize
50 .with_error(anyhow::anyhow!("invalid replacement config."))
51 })?;
52
53 Self::new(pattern, replacement)
54 }
55}
56
57impl CharacterFilter for RegexCharacterFilter {
58 fn name(&self) -> &'static str {
59 REGEX_CHARACTER_FILTER_NAME
60 }
61
62 fn apply(&self, text: &mut String) -> LinderaResult<OffsetMapping> {
64 let mut filtered_text = String::with_capacity(text.len());
65 let mut mapping = OffsetMapping::new();
66 let mut last_match_end = 0;
67
68 for mat in self.regex.find_iter(text) {
69 let input_start = mat.start();
70 let input_len = mat.len();
71 let replacement_text = self.replacement.as_str();
72 let replacement_len = replacement_text.len();
73
74 filtered_text.push_str(&text[last_match_end..input_start]);
76
77 if input_len != replacement_len {
79 let transformation = Transformation::new(
80 input_start,
81 input_start + input_len,
82 filtered_text.len(),
83 filtered_text.len() + replacement_len,
84 );
85 mapping.add_transformation(transformation);
86 }
87
88 filtered_text.push_str(replacement_text);
90
91 last_match_end = input_start + input_len;
92 }
93
94 filtered_text.push_str(&text[last_match_end..]);
96
97 *text = filtered_text;
98 Ok(mapping)
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use crate::character_filter::CharacterFilter;
105 use crate::character_filter::regex::{RegexCharacterFilter, RegexCharacterFilterConfig};
106
107 #[test]
108 fn test_regex_character_filter_config() {
109 let config_str = r#"
110 {
111 "pattern": "リンデラ",
112 "replacement": "Lindera"
113 }
114 "#;
115 let result: Result<RegexCharacterFilterConfig, _> = serde_json::from_str(config_str);
116 assert!(result.is_ok());
117 }
118
119 #[test]
120 fn test_regex_character_filter_from_config() {
121 let config_str = r#"
122 {
123 "pattern": "リンデラ",
124 "replacement": "Lindera"
125 }
126 "#;
127 let config: RegexCharacterFilterConfig = serde_json::from_str(config_str).unwrap();
128
129 let result = RegexCharacterFilter::from_config(&config);
130 assert!(result.is_ok());
131 }
132
133 #[test]
134 fn test_regex_character_filter_apply() {
135 {
136 let config_str = r#"
137 {
138 "pattern": "リンデラ",
139 "replacement": "Lindera"
140 }
141 "#;
142 let config: RegexCharacterFilterConfig = serde_json::from_str(config_str).unwrap();
143
144 let filter = RegexCharacterFilter::from_config(&config).unwrap();
145 let original_text = "リンデラは形態素解析器です。";
146 let mut text = original_text.to_string();
147 let mapping = filter.apply(&mut text).unwrap();
148 assert_eq!("Linderaは形態素解析器です。", text.as_str());
149
150 assert_eq!(1, mapping.transformations.len());
152 let transform = &mapping.transformations[0];
153 assert_eq!(0, transform.original_start);
154 assert_eq!(12, transform.original_end);
155 assert_eq!(0, transform.filtered_start);
156 assert_eq!(7, transform.filtered_end);
157
158 let start = 0;
160 let end = 7;
161 assert_eq!("Lindera", &text[start..end]);
162 let correct_start = mapping.correct_offset(start, text.len());
163 let correct_end = mapping.correct_offset(end, text.len());
164 assert_eq!(0, correct_start);
165 assert_eq!(12, correct_end);
166 assert_eq!("リンデラ", &original_text[correct_start..correct_end]);
167 }
168
169 {
170 let config_str = r#"
171 {
172 "pattern": "\\s{2,}",
173 "replacement": " "
174 }
175 "#;
176 let config: RegexCharacterFilterConfig = serde_json::from_str(config_str).unwrap();
177
178 let filter = RegexCharacterFilter::from_config(&config).unwrap();
179 let original_text = "a b c";
180 let mut text = original_text.to_string();
181 let mapping = filter.apply(&mut text).unwrap();
182 assert_eq!("a b c", text.as_str());
183
184 assert_eq!(2, mapping.transformations.len());
186 let transform1 = &mapping.transformations[0];
187 assert_eq!(1, transform1.original_start);
188 assert_eq!(6, transform1.original_end);
189 assert_eq!(1, transform1.filtered_start);
190 assert_eq!(2, transform1.filtered_end);
191
192 let transform2 = &mapping.transformations[1];
193 assert_eq!(7, transform2.original_start);
194 assert_eq!(12, transform2.original_end);
195 assert_eq!(3, transform2.filtered_start);
196 assert_eq!(4, transform2.filtered_end);
197
198 let start = 2;
200 let end = 3;
201 assert_eq!("b", &text[start..end]);
202 let correct_start = mapping.correct_offset(start, text.len());
203 let correct_end = mapping.correct_offset(end, text.len());
204 assert_eq!(6, correct_start);
205 assert_eq!(7, correct_end);
206 assert_eq!("b", &original_text[correct_start..correct_end]);
207 }
208 }
209}