Skip to main content

string_patterns/
words.rs

1use crate::{PatternReplace, utils::{build_whole_word_pattern, build_word_pattern, build_optional_whole_word_pattern}, WordBounds, PatternMatch, PatternCapture};
2
3// Set of traits with extension methods to match or replace one or more whole words or sets of whole words
4// with various word boundary and case-sensitivity rules
5
6/// Provides methods to match words with differnt word boundary and case-semsitivity rules 
7pub trait MatchWord<'a> where Self:PatternMatch, Self:PatternCapture<'a> {
8
9  /// Match a word with bounds options and case_insensitive flag
10  fn match_word_bounds(&self, word: &str, bounds: WordBounds, case_insensitive: bool) -> bool {
11    let word_pattern = bounds.to_pattern(word);
12    self.pattern_match(&word_pattern, case_insensitive)
13  }
14
15   /// Case-conditional match of a whole word
16  /// To match only the start or end, use the start and end methods or expand the pattern with \w* at either end
17  fn match_word(&self, word: &str, case_insensitive: bool) -> bool {
18    let pattern = build_whole_word_pattern(word);
19    self.pattern_match(&pattern, case_insensitive)
20  }
21
22  /// Match any whole words only with a boolean case_insensitive flag
23  fn match_any_words(&self, words: &[&str], case_insensitive: bool) -> bool {
24    let pattern = build_optional_whole_word_pattern(words);
25    self.pattern_match(&pattern, case_insensitive)
26  }
27
28  /// Case-conditional match from the start of a word boundary
29  fn match_word_start(&self, word: &str, case_insensitive: bool) -> bool {
30    let word_pattern = build_word_pattern(word, WordBounds::Start);
31    self.pattern_match(&word_pattern, case_insensitive)
32  }
33
34  /// Case-conditional match to the end of a word boundary
35  fn match_word_end(&self, word: &str, case_insensitive: bool) -> bool {
36    let word_pattern = build_word_pattern(word, WordBounds::End);
37    self.pattern_match(&word_pattern, case_insensitive)
38  }
39  
40  /// Case-insensitive whole word match, for words with optional hyphens use -?, e.g. hip-?hop matches hip-hop and hiphop, but not hip-hopping
41  /// To match only the start or end, use the start and end methods or expand the pattern with \w* at either end
42  fn match_word_ci(&self, word: &str) -> bool {
43    self.match_word(word, true)
44  }
45
46  /// Match any whole words only in case-insensitive mode
47  fn match_any_words_ci(&self, words: &[&str]) -> bool {
48    let pattern = build_optional_whole_word_pattern(words);
49    self.pattern_match(&pattern, true)
50  }
51
52  /// Match any whole words only in case-sensitive mode
53  fn match_any_words_cs(&self, words: &[&str]) -> bool {
54    let pattern = build_optional_whole_word_pattern(words);
55    self.pattern_match(&pattern, false)
56  }
57
58  /// Case-insensitive match from the start of a word boundary
59  fn match_word_start_ci(&self, word: &str) -> bool {
60    self.match_word_start(word, true)
61  }
62
63  /// Case-insensitive match to the end of a word boundary
64  fn match_word_end_ci(&self, word: &str) -> bool {
65    self.match_word_end(word, true)
66  }
67  /// Case-sensitive whole word match, for words with optional hyphens use -?, e.g. hip-?hop matches hip-hop and hiphop, but not hip-hopping
68  /// To match only the start or end, use the start and end methods or expand the pattern with \w* at either end
69  fn match_word_cs(&self, word: &str) -> bool {
70    self.match_word(word, false)
71  }
72
73  /// Case-sensitive match from the start of a word boundary
74  fn match_word_start_cs(&self, word: &str) -> bool {
75    self.match_word_start(word, false)
76  }
77
78  /// Match all whole words in case-insensitive mode
79  fn match_words_ci(&self, words: &[&str]) -> bool {
80    self.match_words_bounds(words, WordBounds::Both, true)
81  }
82
83  /// Match all whole words in case-sensitive mode
84  fn match_words_cs(&self, words: &[&str]) -> bool {
85    self.match_words_bounds(words, WordBounds::Both, false)
86  }
87
88  /// Case-sensitive match to the end of a word boundary
89  fn match_word_end_cs(&self, word: &str) -> bool {
90    self.match_word_end(word, false)
91  }
92
93  /// Count matched words from an array of strs with boundary and case_insensitive options
94  fn count_matched_words_bounds(&self, words: &[&str], bounds: WordBounds, case_insensitive: bool) -> usize {
95    let mut num_matched = 0;
96    for word in words {
97      let pattern = bounds.to_pattern(word);
98      if self.pattern_match(&pattern, case_insensitive) {
99        num_matched += 1;
100      }
101    }
102    num_matched
103  }
104
105  /// Match all words in array with boundary and case_insensitive options
106  fn match_words_bounds(&self, words: &[&str], bounds: WordBounds, case_insensitive: bool) -> bool {
107    words.len() == self.count_matched_words_bounds(words, bounds, case_insensitive)
108  }
109
110  /// Match all whole words only with a boolean case_insensitive flag
111  fn match_words(&self, words: &[&str], case_insensitive: bool) -> bool {
112    self.match_words_bounds(words, WordBounds::Both, case_insensitive)
113  }
114
115   /// Match sets of words with positivity, pattern and case_insensitive parameters in tuples
116  /// e.g. to match sentences with cat(s) but not dog(s) (lower case only)
117  /// let sets = [(true, "cats?", true), (false, "dogs?", false)]; 
118  fn match_words_sets_conditional(&self, sets: &[(bool, &str, bool)]) -> bool {
119    let num_words = sets.len();
120    let mut num_matched = 0;
121    for row in sets {
122      let (is_positive, word, case_insensitive) = *row;
123      let pattern = build_whole_word_pattern(word);
124      if self.pattern_match(&pattern, case_insensitive) == is_positive {
125        num_matched += 1;
126      }
127    }
128    num_matched == num_words
129  }
130
131  /// Match sets of words with positivity and pattern tuple in case-insensitive mode
132  /// e.g. to match sentences with cat(s) but not dog(s) (lower case only)
133  /// let sets = [(true, "cats?"), (false, "dogs?")];
134  fn match_words_sets_conditional_ci(&self, tuples: &[(bool, &str)]) -> bool {
135    let num_words = tuples.len();
136    let mut num_matched = 0;
137    for row in tuples {
138      let (is_positive, word) = *row;
139      let pattern = build_whole_word_pattern(word);
140      if self.pattern_match(&pattern, true) == is_positive {
141        num_matched += 1;
142      }
143    }
144    num_matched == num_words
145  }
146
147}
148
149/// Automatic implementation for any string-like type, since PatternMatch and PatternCapture
150/// are both implemented generically for T: `AsRef<str>` in this crate.
151impl<'a, T: AsRef<str>> MatchWord<'a> for T {
152}
153
154/// Methods for whole or partial word replacements
155pub trait ReplaceWord where Self:PatternReplace {
156
157  /// Replace words with boundary and case_insensitive options
158  fn replace_word_bounds(&self, word: &str, replacement: &str, bounds: WordBounds, case_insensitive: bool) -> Self where Self:Sized;
159
160  /// Replace whole words with case_insensitive options
161  fn replace_word(&self, word: &str, replacement: &str, case_insensitive: bool) -> Self where Self:Sized;
162
163  /// Replace whole words with in case-insensitive mode
164  fn replace_word_ci(&self, word: &str, replacement: &str) -> Self where Self:Sized {
165    let pattern = build_whole_word_pattern(word);
166    self.pattern_replace(&pattern, replacement, true)
167  }
168
169  /// Replace whole words with in case-sensitive mode
170  fn replace_word_cs(&self, word: &str, replacement: &str) -> Self where Self:Sized {
171    let pattern = build_whole_word_pattern(word);
172    self.pattern_replace(&pattern, replacement, false)
173  }
174
175  /// Replace one or pairs of whole words with a boolean case_insensitive flag
176  fn replace_words(&self, pairs: &[(&str, &str)], case_insensitive: bool) -> Self where Self:Sized;
177
178  /// Replace one or pairs of whole words in case-insensitive mode
179  fn replace_words_ci(&self, pairs: &[(&str, &str)]) -> Self where Self:Sized {
180    self.replace_words(pairs, true)
181  }
182
183  /// Replace one or pairs of whole words in case-sensitive mode
184  fn replace_words_cs(&self, pairs: &[(&str, &str)]) -> Self where Self:Sized {
185    self.replace_words(pairs, false)
186  }
187
188  /// Replace one or sets of whole words with case_insensitive flags as the last tuple element
189  fn replace_word_sets(&self, pairs: &[(&str, &str, bool)]) -> Self where Self:Sized;
190
191}
192
193
194/// Methods for whole or partial word replacements
195impl ReplaceWord for String {
196
197  /// Replace words with boundary and case_insensitive options
198  fn replace_word_bounds(&self, word: &str, replacement: &str, bounds: WordBounds, case_insensitive: bool) -> String {
199    let pattern = build_word_pattern(word, bounds);
200    self.pattern_replace(&pattern, replacement, case_insensitive)
201  }
202
203  /// Replace whole words with case_insensitive options
204  fn replace_word(&self, word: &str, replacement: &str, case_insensitive: bool) -> String {
205    let pattern = build_whole_word_pattern(word);
206    self.pattern_replace(&pattern, replacement, case_insensitive)
207  }
208  
209  /// Replace one or pairs of whole words with a boolean case_insensitive flag
210  fn replace_words(&self, pairs: &[(&str, &str)], case_insensitive: bool) -> String {
211    let mut output = self.clone();
212    for pair in pairs {
213      let (word, replacement) = *pair;
214      let pattern = build_whole_word_pattern(word);
215      output = output.pattern_replace(&pattern, replacement, case_insensitive);
216    }
217    output
218  }
219
220  /// Replace one or sets of whole words with case_insensitive flags as the last tuple element
221  fn replace_word_sets(&self, tuples: &[(&str, &str, bool)]) -> String {
222    let mut output = self.clone();
223    for row in tuples {
224      let (word, replacement, case_insensitive) = *row;
225      let pattern = build_whole_word_pattern(word);
226      output = output.pattern_replace(&pattern, replacement, case_insensitive);
227    }
228    output
229  }
230
231}