1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
use crate::{PatternReplace, utils::{build_whole_word_pattern, build_word_pattern, build_optional_whole_word_pattern}, WordBounds, PatternMatch, PatternCapture};

// Set of traits with extension methods to match or replace one or more whole words or sets of whole words
// with various word boundary and case-sensitivity rules

/// Provides methods to match words with differnt word boundary and case-semsitivity rules 
pub trait MatchWord<'a> where Self:PatternMatch, Self:PatternCapture<'a> {

  /// Match a word with bounds options and case_insensitive flag
  fn match_word_bounds(&self, word: &str, bounds: WordBounds, case_insensitive: bool) -> bool {
    let word_pattern = bounds.to_pattern(word);
    self.pattern_match(&word_pattern, case_insensitive)
  }

   /// Case-conditional match of a whole word
  /// To match only the start or end, use the start and end methods or expand the pattern with \w* at either end
  fn match_word(&self, word: &str, case_insensitive: bool) -> bool {
    let pattern = build_whole_word_pattern(word);
    self.pattern_match(&pattern, case_insensitive)
  }

  /// Match any whole words only with a boolean case_insensitive flag
  fn match_any_words(&self, words: &[&str], case_insensitive: bool) -> bool {
    let pattern = build_optional_whole_word_pattern(words);
    self.pattern_match(&pattern, case_insensitive)
  }

  /// Case-conditional match from the start of a word boundary
  fn match_word_start(&self, word: &str, case_insensitive: bool) -> bool {
    let word_pattern = build_word_pattern(word, WordBounds::Start);
    self.pattern_match(&word_pattern, case_insensitive)
  }

  /// Case-conditional match to the end of a word boundary
  fn match_word_end(&self, word: &str, case_insensitive: bool) -> bool {
    let word_pattern = build_word_pattern(word, WordBounds::End);
    self.pattern_match(&word_pattern, case_insensitive)
  }
  
  /// Case-insensitive whole word match, for words with optional hyphens use -?, e.g. hip-?hop matches hip-hop and hiphop, but not hip-hopping
  /// To match only the start or end, use the start and end methods or expand the pattern with \w* at either end
  fn match_word_ci(&self, word: &str) -> bool {
    self.match_word(word, true)
  }

  /// Match any whole words only in case-insensitive mode
  fn match_any_words_ci(&self, words: &[&str]) -> bool {
    let pattern = build_optional_whole_word_pattern(words);
    self.pattern_match(&pattern, true)
  }

  /// Match any whole words only in case-sensitive mode
  fn match_any_words_cs(&self, words: &[&str]) -> bool {
    let pattern = build_optional_whole_word_pattern(words);
    self.pattern_match(&pattern, false)
  }

  /// Case-insensitive match from the start of a word boundary
  fn match_word_start_ci(&self, word: &str) -> bool {
    self.match_word_start(word, true)
  }

  /// Case-insensitive match to the end of a word boundary
  fn match_word_end_ci(&self, word: &str) -> bool {
    self.match_word_end(word, true)
  }
  /// Case-sensitive whole word match, for words with optional hyphens use -?, e.g. hip-?hop matches hip-hop and hiphop, but not hip-hopping
  /// To match only the start or end, use the start and end methods or expand the pattern with \w* at either end
  fn match_word_cs(&self, word: &str) -> bool {
    self.match_word(word, false)
  }

  /// Case-sensitive match from the start of a word boundary
  fn match_word_start_cs(&self, word: &str) -> bool {
    self.match_word_start(word, false)
  }

  /// Match all whole words in case-insensitive mode
  fn match_words_ci(&self, words: &[&str]) -> bool {
    self.match_words_bounds(words, WordBounds::Both, true)
  }

  /// Match all whole words in case-sensitive mode
  fn match_words_cs(&self, words: &[&str]) -> bool {
    self.match_words_bounds(words, WordBounds::Both, false)
  }

  /// Case-sensitive match to the end of a word boundary
  fn match_word_end_cs(&self, word: &str) -> bool {
    self.match_word_end(word, false)
  }

  /// Count matched words from an array of strs with boundary and case_insensitive options
  fn count_matched_words_bounds(&self, words: &[&str], bounds: WordBounds, case_insensitive: bool) -> usize {
    let mut num_matched = 0;
    for word in words {
      let pattern = bounds.to_pattern(word);
      if self.pattern_match(&pattern, case_insensitive) {
        num_matched += 1;
      }
    }
    num_matched
  }

  /// Match all words in array with boundary and case_insensitive options
  fn match_words_bounds(&self, words: &[&str], bounds: WordBounds, case_insensitive: bool) -> bool {
    words.len() == self.count_matched_words_bounds(words, bounds, case_insensitive)
  }

  /// Match all whole words only with a boolean case_insensitive flag
  fn match_words(&self, words: &[&str], case_insensitive: bool) -> bool {
    self.match_words_bounds(words, WordBounds::Both, case_insensitive)
  }

   /// Match sets of words with positivity, pattern and case_insensitive parameters in tuples
  /// e.g. to match sentences with cat(s) but not dog(s) (lower case only)
  /// let sets = [(true, "cats?", true), (false, "dogs?", false)]; 
  fn match_words_sets_conditional(&self, sets: &[(bool, &str, bool)]) -> bool {
    let num_words = sets.len();
    let mut num_matched = 0;
    for row in sets {
      let (is_positive, word, case_insensitive) = *row;
      let pattern = build_whole_word_pattern(word);
      if self.pattern_match(&pattern, case_insensitive) == is_positive {
        num_matched += 1;
      }
    }
    num_matched == num_words
  }

  /// Match sets of words with positivity and pattern tuple in case-insensitive mode
  /// e.g. to match sentences with cat(s) but not dog(s) (lower case only)
  /// let sets = [(true, "cats?"), (false, "dogs?")];
  fn match_words_sets_conditional_ci(&self, tuples: &[(bool, &str)]) -> bool {
    let num_words = tuples.len();
    let mut num_matched = 0;
    for row in tuples {
      let (is_positive, word) = *row;
      let pattern = build_whole_word_pattern(word);
      if self.pattern_match(&pattern, true) == is_positive {
        num_matched += 1;
      }
    }
    num_matched == num_words
  }

}

/// Automatic implementation for str/String as both implement PatternMatch and PatternCapture in this crate
impl<'a> MatchWord<'a> for str {
}

/// Methods for whole or partial word replacements
pub trait ReplaceWord where Self:PatternReplace {

  /// Replace words with boundary and case_insensitive options
  fn replace_word_bounds(&self, word: &str, replacement: &str, bounds: WordBounds, case_insensitive: bool) -> Self where Self:Sized;

  /// Replace whole words with case_insensitive options
  fn replace_word(&self, word: &str, replacement: &str, case_insensitive: bool) -> Self where Self:Sized;

  /// Replace whole words with in case-insensitive mode
  fn replace_word_ci(&self, word: &str, replacement: &str) -> Self where Self:Sized {
    let pattern = build_whole_word_pattern(word);
    self.pattern_replace(&pattern, replacement, true)
  }

  /// Replace whole words with in case-sensitive mode
  fn replace_word_cs(&self, word: &str, replacement: &str) -> Self where Self:Sized {
    let pattern = build_whole_word_pattern(word);
    self.pattern_replace(&pattern, replacement, false)
  }

  /// Replace one or pairs of whole words with a boolean case_insensitive flag
  fn replace_words(&self, pairs: &[(&str, &str)], case_insensitive: bool) -> Self where Self:Sized;

  /// Replace one or pairs of whole words in case-insensitive mode
  fn replace_words_ci(&self, pairs: &[(&str, &str)]) -> Self where Self:Sized {
    self.replace_words(pairs, true)
  }

  /// Replace one or pairs of whole words in case-sensitive mode
  fn replace_words_cs(&self, pairs: &[(&str, &str)]) -> Self where Self:Sized {
    self.replace_words(pairs, false)
  }

  /// Replace one or sets of whole words with case_insensitive flags as the last tuple element
  fn replace_word_sets(&self, pairs: &[(&str, &str, bool)]) -> Self where Self:Sized;

}


/// Methods for whole or partial word replacements
impl ReplaceWord for String {

  /// Replace words with boundary and case_insensitive options
  fn replace_word_bounds(&self, word: &str, replacement: &str, bounds: WordBounds, case_insensitive: bool) -> String {
    let pattern = build_word_pattern(word, bounds);
    self.pattern_replace(&pattern, replacement, case_insensitive)
  }

  /// Replace whole words with case_insensitive options
  fn replace_word(&self, word: &str, replacement: &str, case_insensitive: bool) -> String {
    let pattern = build_whole_word_pattern(word);
    self.pattern_replace(&pattern, replacement, case_insensitive)
  }
  
  /// Replace one or pairs of whole words with a boolean case_insensitive flag
  fn replace_words(&self, pairs: &[(&str, &str)], case_insensitive: bool) -> String {
    let mut output = self.clone();
    for pair in pairs {
      let (word, replacement) = *pair;
      let pattern = build_whole_word_pattern(word);
      output = output.pattern_replace(&pattern, replacement, case_insensitive);
    }
    output
  }

  /// Replace one or sets of whole words with case_insensitive flags as the last tuple element
  fn replace_word_sets(&self, tuples: &[(&str, &str, bool)]) -> String {
    let mut output = self.clone();
    for row in tuples {
      let (word, replacement, case_insensitive) = *row;
      let pattern = build_whole_word_pattern(word);
      output = output.pattern_replace(&pattern, replacement, case_insensitive);
    }
    output
  }

}