@@ -35,11 +35,14 @@ inline std::string tolower(std::string s) {
3535 return s;
3636}
3737
38- inline bool ends_with (const std::string& s, const std::string& suf) {
39- if (s.size () < suf.size ()) return false ;
40- std::string a = tolower (s.substr (s.size () - suf.size ()));
41- std::string b = tolower (suf);
42- return a == b;
38+ inline std::string to_lower_ascii (std::string s) {
39+ for (char & c : s)
40+ c = static_cast <char >(std::tolower (static_cast <unsigned char >(c)));
41+ return s;
42+ }
43+
44+ inline bool is_word_char (unsigned char c) {
45+ return std::isalnum (c) || c == ' _' ;
4346}
4447
4548inline bool contains_string (const std::string& text,
@@ -48,17 +51,25 @@ inline bool contains_string(const std::string& text,
4851 return h.find (n) != std::string::npos;
4952}
5053
54+ inline bool ends_with (const std::string& s, const std::string& suf,
55+ unsigned threshold) {
56+ if (s.size () < suf.size ()) return false ;
57+ std::string a = tolower (s.substr (s.size () - (suf.size () + threshold)));
58+ std::string b = tolower (suf);
59+ return threshold == 0 ? a == b : contains_string (a, b);
60+ }
61+
62+ inline bool starts_with (const std::string& s, const std::string& prf,
63+ unsigned threshold) {
64+ if (s.size () < prf.size ()) return false ;
65+ std::string a = tolower (s.substr (0 , prf.size () + threshold));
66+ std::string b = tolower (prf);
67+ return threshold == 0 ? a == b : contains_string (a, b);
68+ }
69+
5170inline bool contains_word (const std::string& text, const std::string& word) {
5271 if (word.empty ()) return false ;
5372
54- auto to_lower_ascii = [](std::string s) {
55- for (char & c : s) c = std::tolower (static_cast <unsigned char >(c));
56- return s;
57- };
58- auto is_word_char = [](unsigned char c) {
59- return std::isalnum (c) || c == ' _' ; // match std::regex \b notion of "word"
60- };
61-
6273 std::string t = to_lower_ascii (text);
6374 std::string w = to_lower_ascii (word);
6475
@@ -83,6 +94,39 @@ inline bool contains_none(const std::string& text,
8394 return true ;
8495}
8596
97+ inline size_t find_containing_word (const std::string& text,
98+ const std::string& keyword,
99+ std::string& containing_word, size_t pos) {
100+ if (keyword.empty () || pos >= text.size ()) return std::string::npos;
101+
102+ std::string t = to_lower_ascii (text);
103+ std::string k = to_lower_ascii (keyword);
104+
105+ if ((pos = t.find (k, pos)) == std::string::npos) return std::string::npos;
106+
107+ // Expand left to word boundary
108+ size_t start = pos;
109+ while (start > 0 && is_word_char (static_cast <unsigned char >(t[start - 1 ]))) {
110+ --start;
111+ }
112+
113+ // Expand right to word boundary
114+ size_t end = pos + k.size ();
115+ while (end < t.size () && is_word_char (static_cast <unsigned char >(t[end]))) {
116+ ++end;
117+ }
118+
119+ // Extract original (not lowercased) word
120+ containing_word = text.substr (start, end - start);
121+ return start;
122+ }
123+
124+ inline size_t find_containing_word (const std::string& text,
125+ const std::string& keyword,
126+ std::string& out_word) {
127+ return find_containing_word (text, keyword, out_word, 0 );
128+ }
129+
86130inline std::string remove_font_modifiers (const std::string& s) {
87131 std::string out;
88132 out.reserve (s.size ());
@@ -115,14 +159,12 @@ inline std::string remove_font_modifiers(const std::string& s) {
115159
116160inline std::string remove_first_line (const std::string& s) {
117161 std::size_t pos = s.find (' \n ' );
118- return (pos == std::string::npos) ? std::string{} : s.substr (pos + 1 );
119- // If there is no newline, removing the first line yields empty.
162+ return (pos == std::string::npos) ? std::string (s) : s.substr (pos + 1 );
120163}
121164
122165inline std::string remove_last_line (const std::string& s) {
123166 std::size_t pos = s.rfind (' \n ' );
124- return (pos == std::string::npos) ? std::string{} : s.substr (0 , pos);
125- // If there is no newline, removing the last line yields empty.
167+ return (pos == std::string::npos) ? std::string (s) : s.substr (0 , pos);
126168}
127169
128170// Returns the 8 transformations as an array of strings.
0 commit comments