CommonLib
Loading...
Searching...
No Matches
string.inl
Go to the documentation of this file.
1#include <algorithm>
2#include <charconv>
3#include <filesystem>
4#include <iomanip>
5#include <optional>
6#include <regex>
7#include <string>
8#include <type_traits>
9#include <vector>
10
11#include "expr/expr.h"
12#include "expr/string_expr.h"
13#include "encoding.h"
14
15namespace hedgedev::csl::ut::string
16{
17 template <expr::any_string T_left, expr::any_string T_right>
18 inline bool compare(const T_left& in_left, const T_right& in_right, bool in_case_sensitive)
19 {
20 const auto strings = encoding::precedent_convert(in_left, in_right);
21
22 const auto& left = strings[0];
23 const auto& right = strings[1];
24
25 if (left.length() != right.length())
26 return false;
27
28 if (!in_case_sensitive)
29 return lower(left) == lower(right);
30
31 return left == right;
32 }
33
34 template <expr::any_string T_str, expr::any_string T_substr>
35 inline bool contains(const T_str& in_str, const T_substr& in_substr, bool in_case_sensitive)
36 {
37 const auto strings = encoding::precedent_convert(in_str, in_substr);
38
39 const auto& str = strings[0];
40 const auto& substr = strings[1];
41
42 if (str.length() < substr.length())
43 return false;
44
45 using pi_string_t = expr::pi_string_t<T_str, T_substr>;
46
47 if (!in_case_sensitive)
48 {
49 using pi_string_char_t = expr::get_char_type_t<pi_string_t>;
50
51 const auto it = std::search(str.begin(), str.end(), substr.begin(), substr.end(),
52 //
53 [](pi_string_char_t in_left, pi_string_char_t in_right) -> bool
54 {
55 return std::tolower(in_left) == std::tolower(in_right);
56 }
57 );
58
59 return it != str.end();
60 }
61
62 return str.find(substr) != pi_string_t::npos;
63 }
64
65 template <expr::any_string T_str, typename T_str_char>
67 {
68 std::basic_stringstream<T_str_char> result{};
69
70 for (const auto& c : expr::inferred_string_view_t(in_str))
71 {
72 if (c == in_search_char)
73 result << in_escape_char;
74
75 result << c;
76 }
77
78 return result.str();
79 }
80
81 template <expr::any_string T>
82 inline expr::inferred_string_t<T> format(const T in_str, ...)
83 {
84 va_list args;
85 va_start(args, in_str);
86
87 using str_char_t = expr::get_char_type_t<T>;
88
89 const auto str_sv = expr::inferred_string_view_t<T>(in_str);
90
91 size_t length = sizeof(str_char_t);
92
93 if constexpr (std::is_same_v<str_char_t, char>)
94 {
95 length += _vscprintf(str_sv.data(), args);
96 }
97 else if constexpr (std::is_same_v<str_char_t, wchar_t>)
98 {
99 length += _vscwprintf(str_sv.data(), args);
100 }
101 else
102 {
103 static_assert(false, "Unsupported string type.");
104 }
105
106 const auto buffer = std::make_unique<str_char_t[]>(length);
107
108 if (length > 0)
109 {
110 if constexpr (std::is_same_v<str_char_t, char>)
111 {
112 _vsnprintf_s(buffer.get(), length, _TRUNCATE, str_sv.data(), args);
113 }
114 else if constexpr (std::is_same_v<str_char_t, wchar_t>)
115 {
116 _vsnwprintf_s(buffer.get(), length, _TRUNCATE, str_sv.data(), args);
117 }
118 }
119
120 va_end(args);
121
122 return expr::inferred_string_t<T>(buffer.get(), length);
123 }
124
125 template <expr::any_string T>
126 inline size_t get_width(const T& in_str)
127 {
128 size_t result{};
129
130 std::basic_stringstream<expr::get_char_type_t<T>> stream(in_str);
131 expr::inferred_string_t<T> line{};
132
133 while (std::getline(stream, line))
134 result = std::max(result, line.length());
135
136 return result;
137 }
138
139 template <expr::any_string T_str, expr::any_string T_url, typename T_result>
140 inline T_result hyperlink(const T_str& in_str, const T_url& in_url)
141 {
142 const auto strings = encoding::precedent_convert(in_str, in_url);
143
144 const auto& str = strings[0];
145 const auto& url = strings[1];
146
147 if (url.empty())
148 return T_result(str);
149
150 std::basic_stringstream<expr::get_char_type_t<T_result>> result{};
151
152 result << expr::create_inferred_string<T_result>("<a href=\"")
153 << url << expr::create_inferred_string<T_result>("\">")
154 << str << expr::create_inferred_string<T_result>("</a>");
155
156 return result.str();
157 }
158
159 template <expr::any_string T>
160 inline bool is_null_or_empty(const T& in_str)
161 {
162 if constexpr (std::is_pointer_v<std::remove_cvref_t<T>>)
163 {
164 if (!in_str)
165 return true;
166 }
167
168 return expr::inferred_string_view_t<T>(in_str).empty();
169 }
170
171 template <expr::any_string T>
172 inline bool is_null_or_whitespace(const T& in_str)
173 {
174 if (is_null_or_empty(in_str))
175 return true;
176
177 const auto str_sv = expr::inferred_string_view_t<T>(in_str);
178
179 return std::all_of(str_sv.begin(), str_sv.end(), [](expr::get_char_type_t<T> in_char) -> bool
180 {
181 return std::isspace(in_char);
182 });
183 }
184
185 template <expr::any_string T_delimiter, expr::any_string T_strings>
187 {
188 if constexpr (!expr::is_same_underlying_char_type_v<T_delimiter, T_strings>)
189 static_assert(false, "The delimiter must have the same underlying character type as the collection.");
190
191 std::basic_stringstream<expr::get_char_type_t<T_strings>> result{};
192
193 if (!in_strings.empty())
194 {
195 const auto length = in_strings.size();
196
197 for (size_t i = 0; i < length; i++)
198 {
199 const auto& str = in_strings[i];
200
201 result << str;
202
203 if (i == length - 1)
204 continue;
205
206 result << in_delimiter;
207 }
208 }
209
210 return result.str();
211 }
212
213 template <expr::any_string T_delimiter, expr::any_string... T_args, typename T_result>
214 inline T_result join(const T_delimiter& in_delimiter, const T_args&... in_args)
215 {
216 const auto strings = encoding::precedent_convert(in_delimiter, in_args...);
217
218 return join(strings[0], std::vector(strings.begin() + 1, strings.end()));
219 }
220
221 template <expr::any_string T_str, expr::any_string T_pad_str, typename T_result>
222 inline T_result pad(const T_str& in_str, const T_pad_str& in_pad_str)
223 {
224 std::basic_stringstream<expr::get_char_type_t<T_result>> result{};
225
226 const auto strings = encoding::precedent_convert(in_str, in_pad_str);
227
228 const auto& str = strings[0];
229 const auto& pad_str = strings[1];
230
231 result << pad_str << str << pad_str;
232
233 return result.str();
234 }
235
236 template <typename T_result, expr::any_string T_str>
237 inline T_result parse(const T_str& in_str)
238 {
239 T_result result{};
240
241 try_parse(in_str, result);
242
243 return result;
244 }
245
246 template <expr::any_string T_str>
248 {
249 const auto xml_regex = expr::create_inferred_string<expr::inferred_string_t<T_str>>("<[^>]*>");
250
251 return std::regex_replace(expr::inferred_string_view_t<T_str>(in_str).data(),
252 std::basic_regex<expr::get_char_type_t<T_str>>(xml_regex),
253 expr::inferred_string_t<T_str>());
254 }
255
256 template <expr::any_string T_str, expr::any_string T_delimiter, typename T_result>
258 {
259 std::vector<T_result> result{};
260
261 using pi_string_view_t = expr::pi_string_view_t<T_str, T_delimiter>;
262
263 const auto strings = encoding::precedent_convert(in_str, in_delimiter);
264
265 const auto& str = strings[0];
266 const auto& delimiter = strings[1];
267
268 size_t start{};
269 const auto delimiter_length = delimiter.length();
270
271 while (true)
272 {
273 const auto pos = str.find(delimiter, start);
274 const auto token = str.substr(start, pos == pi_string_view_t::npos ? str.size() - start : pos - start);
275
276 result.emplace_back(trim(token));
277
278 if (pos == pi_string_view_t::npos)
279 break;
280
281 start = pos + delimiter_length;
282 }
283
284 return result;
285 }
286
287 template <expr::any_string T>
289 {
290 auto result = expr::inferred_string_t<T>(in_str);
291
292 if (!result.empty())
293 {
294 std::transform(result.begin(), result.end(), result.begin(),
295 //
296 [](expr::get_char_type_t<T> in_char) -> bool
297 {
298 return std::tolower(in_char);
299 }
300 );
301 }
302
303 return result;
304 }
305
306 template <expr::any_string T>
308 {
309 auto result = expr::inferred_string_t<T>(in_str);
310
311 if (!result.empty())
312 {
313 std::transform(result.begin(), result.end(), result.begin(),
314 //
315 [](expr::get_char_type_t<T> in_char) -> bool
316 {
317 return std::toupper(in_char);
318 }
319 );
320 }
321
322 return result;
323 }
324
325 template <expr::any_string T_str, typename T_trim_chars>
327 {
328 const auto str_sv = expr::inferred_string_view_t<T_str>(in_str);
329
330 const auto it = std::find_if(str_sv.begin(), str_sv.end(),
331 //
332 [&](T_trim_chars in_char) -> bool
333 {
334 if (in_trim_chars.has_value())
335 return std::find(in_trim_chars->begin(), in_trim_chars->end(), in_char) == in_trim_chars->end();
336
337 return !std::isspace(in_char);
338 }
339 );
340
341 return expr::inferred_string_t<T_str>(str_sv.substr(std::distance(str_sv.begin(), it)));
342 }
343
344 template <expr::any_string T_str, typename T_trim_chars>
346 {
347 const auto str_sv = expr::inferred_string_view_t<T_str>(in_str);
348
349 const auto it = std::find_if_not(str_sv.rbegin(), str_sv.rend(),
350 //
351 [&](T_trim_chars in_char) -> bool
352 {
353 if (in_trim_chars.has_value())
354 return std::find(in_trim_chars->begin(), in_trim_chars->end(), in_char) != in_trim_chars->end();
355
356 return std::isspace(in_char);
357 }
358 )
359 .base();
360
361 return expr::inferred_string_t<T_str>(str_sv.substr(0, std::distance(str_sv.begin(), it)));
362 }
363
364 template <expr::any_string T_str, typename T_trim_chars>
365 inline expr::inferred_string_t<T_str> trim(const T_str& in_str, const std::optional<std::vector<T_trim_chars>> in_trim_chars)
366 {
367 return trim_end(trim_start(in_str, in_trim_chars), in_trim_chars);
368 }
369
370 template <expr::any_string T>
372 {
373 using inferred_string_t = expr::inferred_string_t<T>;
374
375 inferred_string_t result{};
376
377 const auto ellipsis = expr::create_inferred_string<inferred_string_t>("...");
378
379 if (in_ellipsis && in_max_length <= ellipsis.length())
380 {
381 // Truncate ellipsis to max length.
382 result = ellipsis.substr(0, in_max_length);
383 }
384 else
385 {
386 const auto str_sv = expr::inferred_string_view_t<T>(in_str);
387
388 if (str_sv.length() <= in_max_length)
389 {
390 result = str_sv;
391 }
392 else
393 {
394 auto length = in_max_length;
395
396 if (in_ellipsis)
397 length -= ellipsis.length();
398
399 if (in_truncate_end)
400 {
401 result = inferred_string_t(str_sv.substr(0, length));
402
403 if (in_ellipsis)
404 result += ellipsis;
405 }
406 else
407 {
408 if (in_ellipsis)
409 result = ellipsis;
410
411 result += inferred_string_t(str_sv.substr(str_sv.length() - length, length));
412 }
413 }
414 }
415
416 return result;
417 }
418
419 template <expr::basic_string T_result, typename T_value>
421 {
422 using string_char_t = expr::get_char_type_t<T_result>;
423
424 std::basic_stringstream<string_char_t> result{};
425
426 if (in_prefix)
427 result << expr::create_inferred_string<T_result>("0x");
428
429 result << std::uppercase << std::hex;
430
431 if (in_max_length)
432 result << std::setw(in_max_length) << std::setfill(string_char_t('0'));
433
434 if constexpr (std::is_pointer_v<T_value>)
435 {
436 result << uintptr_t(in_value);
437 }
438 else
439 {
440 result << in_value;
441 }
442
443 return result.str();
444 }
445
446 template <typename T_result, expr::any_string T_str>
447 inline bool try_parse(const T_str& in_str, T_result& out_result)
448 {
449 if (is_null_or_whitespace(in_str))
450 return false;
451
452 if constexpr (std::is_same_v<T_str, T_result>)
453 {
454 out_result = in_str;
455 return true;
456 }
457 else if constexpr (std::is_same_v<T_result, std::filesystem::path>)
458 {
459 out_result = std::filesystem::path(in_str);
460 return true;
461 }
462 else if constexpr (expr::any_string<T_result>)
463 {
464 T_result result{};
465
466 if (!try_convert<T_result>(in_str, result))
467 return false;
468
469 out_result = result;
470
471 return true;
472 }
473
474 const auto str_trim = trim(in_str);
475
476 if constexpr (std::is_same_v<T_result, bool>)
477 {
478 const auto str_trim_lower = lower(str_trim);
479
480 out_result = str_trim_lower == expr::create_inferred_string<expr::inferred_string_t<T_str>>("true") ||
481 str_trim_lower == expr::create_inferred_string<expr::inferred_string_t<T_str>>("1");
482
483 return true;
484 }
485 else if constexpr (std::is_integral_v<T_result> || std::is_enum_v<T_result> || std::is_same_v<T_result, float> || std::is_same_v<T_result, double>)
486 {
487 typename std::conditional_t<std::is_enum_v<T_result>, std::underlying_type<T_result>, std::type_identity<T_result>>::type result{};
488 std::string chars{};
489
490 if (!try_convert<std::string>(str_trim, chars))
491 return false;
492
493 if (std::from_chars(chars.data(), chars.data() + chars.size(), result).ec != std::errc{})
494 return false;
495
496 out_result = T_result(result);
497
498 return true;
499 }
500
501 return false;
502 }
503
504 template <expr::any_string T_str>
506 {
507 expr::inferred_string_t<T_str> result{};
508
509 const auto str_sv = expr::inferred_string_view_t<T_str>(in_str);
510
511 using string_char_t = expr::get_char_type_t<T_str>;
512
513 static constexpr auto line_break = string_char_t('\n');
514 static constexpr auto carriage_return = string_char_t('\r');
515
516 static constexpr string_char_t word_chars[] =
517 {
518 '.', ',', ';', '!', '?', '\"', '\\', '/'
519 };
520
521 const string_char_t* pos = str_sv.data();
522 const string_char_t* end = str_sv.data() + str_sv.size();
523
524 const string_char_t* last_line_start = pos;
525 const string_char_t* last_word_start{};
526
527 bool is_inside_word = true;
528
529 while (pos < end)
530 {
531 auto c = *pos;
532 auto next_pos = pos + 1;
533
534 if (std::iscntrl(c))
535 {
536 if (c == line_break)
537 {
538 result.append(last_line_start, next_pos);
539
540 pos = next_pos;
541
542 last_line_start = next_pos;
543 last_word_start = nullptr;
544
545 is_inside_word = true;
546
547 continue;
548 }
549
550 if (c == carriage_return)
551 {
552 pos = next_pos;
553 continue;
554 }
555 }
556
557 is_inside_word = !std::isspace(c) &&
558 std::find(std::begin(word_chars), std::end(word_chars), c) != std::end(word_chars);
559
560 if (!is_inside_word)
561 last_word_start = pos;
562
563 if (size_t(pos - last_line_start) >= in_max_width)
564 {
565 if (last_word_start && last_word_start > last_line_start)
566 {
567 // Wrap to last word boundary.
568 result.append(last_line_start, last_word_start);
569 result.push_back(line_break);
570
571 last_line_start = last_word_start;
572 last_word_start = nullptr;
573 }
574 else
575 {
576 // Wrap immediately.
577 result.append(last_line_start, pos);
578 result.push_back(line_break);
579
580 last_line_start = pos;
581 }
582 }
583
584 pos = next_pos;
585 }
586
587 // Handle remaining characters.
588 if (last_line_start < end)
589 result.append(last_line_start, end);
590
591 return result;
592 }
593}
Definition encoding.h:10
Definition string.h:11
expr::inferred_string_t< T_str > trim_start(const T_str &in_str, std::optional< std::vector< T_trim_chars > > in_trim_chars={})
Definition string.inl:326
expr::inferred_string_t< T > format(const T in_str,...)
Definition string.inl:82
expr::inferred_string_t< T > lower(const T &in_str)
Definition string.inl:288
bool is_null_or_empty(const T &in_str)
Definition string.inl:160
T_result hyperlink(const T_str &in_str, const T_url &in_url)
Definition string.inl:140
expr::inferred_string_t< T_str > remove_xml_tags(const T_str &in_str)
Definition string.inl:247
bool is_null_or_whitespace(const T &in_str)
Definition string.inl:172
expr::inferred_string_t< T_str > trim_end(const T_str &in_str, std::optional< std::vector< T_trim_chars > > in_trim_chars={})
Definition string.inl:345
expr::inferred_string_t< T_str > wrap(const T_str &in_str, size_t in_max_width)
Definition string.inl:505
bool compare(const T_left &in_left, const T_right &in_right, bool in_case_sensitive=true)
Definition string.inl:18
T_result join(const T_delimiter &in_delimiter, const T_args &... in_args)
Definition string.inl:214
std::vector< T_result > split(const T_str &in_str, const T_delimiter &in_delimiter)
Definition string.inl:257
expr::inferred_string_t< T > upper(const T &in_str)
Definition string.inl:307
bool try_parse(const T_str &in_str, T_result &out_result)
Definition string.inl:447
expr::inferred_string_t< T_str > escape(const T_str &in_str, T_str_char in_search_char, T_str_char in_escape_char=T_str_char('\\'))
Definition string.inl:66
bool contains(const T_str &in_str, const T_substr &in_substr, bool in_case_sensitive=true)
Definition string.inl:35
T_result pad(const T_str &in_str, const T_pad_str &in_pad_str)
Definition string.inl:222
T_result hex(T_value in_value, size_t in_max_length=sizeof(size_t) *2, bool in_prefix=true)
Definition string.inl:420
T_result parse(const T_str &in_str)
Definition string.inl:237
size_t get_width(const T &in_str)
Definition string.inl:126
expr::inferred_string_t< T > truncate(const T &in_str, size_t in_max_length, bool in_truncate_end=true, bool in_ellipsis=true)
Definition string.inl:371
expr::inferred_string_t< T_strings > join(const T_delimiter &in_delimiter, const std::vector< T_strings > &in_strings)
Definition string.inl:186
Definition expr.h:7