The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
string_utils.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2005 - 2025
3 by Philippe Plantier <ayin@anathas.org>
4 Copyright (C) 2005 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
5 Copyright (C) 2003 by David White <dave@whitevine.net>
6 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY.
14
15 See the COPYING file for more details.
16*/
17
18/**
19 * @file
20 * Various string-routines.
21 */
22
23#include "gettext.hpp"
24#include "log.hpp"
27#include "utils/charconv.hpp"
28#include "utils/general.hpp"
29#include <array>
30#include <limits>
32#include <stdexcept>
33
34#include <boost/algorithm/string.hpp>
35
36static lg::log_domain log_engine("engine");
37#define ERR_GENERAL LOG_STREAM(err, lg::general())
38#define ERR_NG LOG_STREAM(err, log_engine)
39
40namespace utils {
41
42bool isnewline(const char c)
43{
44 return c == '\r' || c == '\n';
45}
46
47// Make sure that we can use Mac, DOS, or Unix style text files on any system
48// and they will work, by making sure the definition of whitespace is consistent
49bool portable_isspace(const char c)
50{
51 // returns true only on ASCII spaces
52 return c == '\r' || c == '\n' || c == ' ' || c == '\t' || c == '\v' || c == '\f';
53}
54
55// Make sure we regard '\r' and '\n' as a space, since Mac, Unix, and DOS
56// all consider these differently.
57bool notspace(const char c)
58{
59 return !portable_isspace(c);
60}
61
62void trim(std::string_view& s)
63{
64 s.remove_prefix(std::min(s.find_first_not_of(" \t\r\n"), s.size()));
65 if(s.empty()) {
66 return;
67 }
68 //find_last_not_of never returns npos because !s.empty()
69 std::size_t first_to_trim = s.find_last_not_of(" \t\r\n") + 1;
70 s = s.substr(0, first_to_trim);
71}
72
73/**
74 * Splits a (comma-)separated string into a vector of pieces.
75 * @param[in] s A (comma-)separated string.
76 * @param[in] sep The separator character (usually a comma).
77 * @param[in] flags Flags controlling how the split is done.
78 * This is a bit field with two settings (both on by default):
79 * REMOVE_EMPTY causes empty pieces to be skipped/removed.
80 * STRIP_SPACES causes the leading and trailing spaces of each piece to be ignored/stripped.
81 */
82std::vector<std::string> split(std::string_view s, const char sep, const int flags)
83{
84 std::vector<std::string> res;
85 split_foreach(s, sep, flags, [&](std::string_view item) {
86 res.emplace_back(item);
87 });
88 return res;
89}
90
91std::set<std::string> split_set(std::string_view s, char sep, const int flags)
92{
93 std::set<std::string> res;
94 split_foreach(s, sep, flags, [&](std::string_view item) {
95 res.emplace(item);
96 });
97 return res;
98}
99
100std::vector<std::string_view> split_view(std::string_view s, const char sep, const int flags)
101{
102 std::vector<std::string_view> res;
103 split_foreach(s, sep, flags, [&](std::string_view item) {
104 res.push_back(item);
105 });
106 return res;
107}
108
109namespace {
110 std::size_t get_padding(std::string_view str)
111 {
112 // A leading '0' signals we want to pad upto the size of the number
113 return (str.size() > 1 && str[0] == '0') ? (str.size() - 1) : 0;
114 }
115}
116
117std::vector<std::string> square_parenthetical_split(const std::string& val,
118 const char separator, const std::string& left,
119 const std::string& right,const int flags)
120{
121 std::vector< std::string > res;
122
123 if (val.empty()) {
124 return res;
125 }
126 if (!separator) {
127 ERR_GENERAL << "Separator must be specified for square bracket split function.";
128 return res;
129 }
130 if (left.size() != right.size()) {
131 ERR_GENERAL << "Left and Right Parenthesis lists not same length";
132 return res;
133 }
134
135 std::string lp = left;
136 std::string rp = right;
137 std::vector<char> part;
138 bool in_parenthesis = false;
139 std::vector<std::string::const_iterator> square_left;
140 std::vector<std::string::const_iterator> square_right;
141 std::vector< std::string > square_expansion;
142
143 std::string::const_iterator i1 = val.begin();
144 std::string::const_iterator i2;
145 std::string::const_iterator j1;
146 if (flags & STRIP_SPACES) {
147 while (i1 != val.end() && portable_isspace(*i1))
148 ++i1;
149 }
150 if (i1 == val.end()) return res;
151 i2=i1;
152 j1=i1;
153
154 // If the string contains no animation markers, return the string immediately.
155 // Added since many static images are treated as animations and run through here.
156 const std::string complex_markers = separator + std::string("[");
157 if (val.find_first_of(complex_markers) == std::string::npos)
158 {
159 std::string mutable_val(i1, val.end());
160 if (flags & STRIP_SPACES) {
161 boost::trim_right(mutable_val);
162 }
163 return { mutable_val };
164 }
165
166 while (true) {
167 if(i2 == val.end() || (!in_parenthesis && *i2 == separator)) {
168 //push back square contents
169 std::size_t size_square_exp = 0;
170 for (std::size_t i=0; i < square_left.size(); i++) {
171 std::string tmp_val(square_left[i]+1,square_right[i]);
172 std::vector< std::string > tmp = split(tmp_val);
173 for(const std::string& piece : tmp) {
174 std::size_t found_tilde = piece.find_first_of('~');
175 if (found_tilde == std::string::npos) {
176 std::size_t found_asterisk = piece.find_first_of('*');
177 if (found_asterisk == std::string::npos) {
178 std::string tmp2(piece);
179 boost::trim(tmp2);
180 square_expansion.push_back(tmp2);
181 }
182 else { //'*' multiple expansion
183 std::string s_begin = piece.substr(0,found_asterisk);
184 boost::trim(s_begin);
185 std::string s_end = piece.substr(found_asterisk+1);
186 boost::trim(s_end);
187 for (int ast=std::stoi(s_end); ast>0; --ast)
188 square_expansion.push_back(s_begin);
189 }
190 }
191 else { //expand number range
192 std::string s_begin = piece.substr(0,found_tilde);
193 boost::trim(s_begin);
194 int begin = std::stoi(s_begin);
195 std::string s_end = piece.substr(found_tilde+1);
196 boost::trim(s_end);
197 int end = std::stoi(s_end);
198
199 std::size_t padding = std::max(get_padding(s_begin), get_padding(s_end));
200 if (padding > 0 && s_begin.size() != s_end.size()) {
201 ERR_GENERAL << "Square bracket padding sizes not matching: "
202 << s_begin << " and " << s_end <<".";
203 }
204
205 int increment = (end >= begin ? 1 : -1);
206 end+=increment; //include end in expansion
207 for (int k=begin; k!=end; k+=increment) {
208 std::string pb = std::to_string(k);
209 for (std::size_t p=pb.size(); p<=padding; p++)
210 pb = std::string("0") + pb;
211 square_expansion.push_back(pb);
212 }
213 }
214 }
215 if (i*square_expansion.size() != (i+1)*size_square_exp ) {
216 std::string tmp2(i1, i2);
217 ERR_GENERAL << "Square bracket lengths do not match up: " << tmp2;
218 return res;
219 }
220 size_square_exp = square_expansion.size();
221 }
222
223 //combine square contents and rest of string for comma zone block
224 std::size_t j = 0;
225 std::size_t j_max = 0;
226 if (!square_left.empty())
227 j_max = square_expansion.size() / square_left.size();
228 do {
229 j1 = i1;
230 std::string new_val;
231 for (std::size_t i=0; i < square_left.size(); i++) {
232 std::string tmp_val(j1, square_left[i]);
233 new_val.append(tmp_val);
234 std::size_t k = j+i*j_max;
235 if (k < square_expansion.size())
236 new_val.append(square_expansion[k]);
237 j1 = square_right[i]+1;
238 }
239 std::string tmp_val(j1, i2);
240 new_val.append(tmp_val);
241 if (flags & STRIP_SPACES)
242 boost::trim_right(new_val);
243 if (!(flags & REMOVE_EMPTY) || !new_val.empty())
244 res.push_back(new_val);
245 j++;
246 } while (j<j_max);
247
248 if (i2 == val.end()) //escape loop
249 break;
250 ++i2;
251 if (flags & STRIP_SPACES) { //strip leading spaces
252 while (i2 != val.end() && portable_isspace(*i2))
253 ++i2;
254 }
255 i1=i2;
256 square_left.clear();
257 square_right.clear();
258 square_expansion.clear();
259 continue;
260 }
261 if(!part.empty() && *i2 == part.back()) {
262 part.pop_back();
263 if (*i2 == ']') square_right.push_back(i2);
264 if (part.empty())
265 in_parenthesis = false;
266 ++i2;
267 continue;
268 }
269 bool found=false;
270 for(std::size_t i=0; i < lp.size(); i++) {
271 if (*i2 == lp[i]){
272 if (*i2 == '[')
273 square_left.push_back(i2);
274 ++i2;
275 part.push_back(rp[i]);
276 found=true;
277 break;
278 }
279 }
280 if(!found){
281 ++i2;
282 } else
283 in_parenthesis = true;
284 }
285
286 if(!part.empty()){
287 ERR_GENERAL << "Mismatched parenthesis:\n"<<val;
288 }
289
290 return res;
291}
292
293std::map<std::string, std::string> map_split(
294 const std::string& val
295 , char major
296 , char minor
297 , int flags
298 , const std::string& default_value)
299{
300 //first split by major so that we get a vector with the key-value pairs
301 std::vector< std::string > v = split(val, major, flags);
302
303 //now split by minor to extract keys and values
304 std::map< std::string, std::string > res;
305
306 for( std::vector< std::string >::iterator i = v.begin(); i != v.end(); ++i) {
307 std::size_t pos = i->find_first_of(minor);
308 std::string key, value;
309
310 if(pos == std::string::npos) {
311 key = (*i);
312 value = default_value;
313 } else {
314 key = i->substr(0, pos);
315 value = i->substr(pos + 1);
316 }
317
318 res[key] = value;
319 }
320
321 return res;
322}
323
324std::vector<std::string> parenthetical_split(std::string_view val,
325 const char separator, std::string_view left,
326 std::string_view right,const int flags)
327{
328 std::vector< std::string > res;
329 std::vector<char> part;
330 bool in_parenthesis = false;
331
332 std::string_view::const_iterator i1 = val.begin();
333 std::string_view::const_iterator i2;
334 if (flags & STRIP_SPACES) {
335 while (i1 != val.end() && portable_isspace(*i1))
336 ++i1;
337 }
338 i2=i1;
339
340 if(left.size()!=right.size()){
341 ERR_GENERAL << "Left and Right Parenthesis lists not same length";
342 return res;
343 }
344
345 while (i2 != val.end()) {
346 if(!in_parenthesis && separator && *i2 == separator){
347 std::string new_val(i1, i2);
348 if (flags & STRIP_SPACES)
349 boost::trim_right(new_val);
350 if (!(flags & REMOVE_EMPTY) || !new_val.empty())
351 res.push_back(new_val);
352 ++i2;
353 if (flags & STRIP_SPACES) {
354 while (i2 != val.end() && portable_isspace(*i2))
355 ++i2;
356 }
357 i1=i2;
358 continue;
359 }
360 if(!part.empty() && *i2 == part.back()){
361 part.pop_back();
362 if(!separator && part.empty()){
363 std::string new_val(i1, i2);
364 if (flags & STRIP_SPACES)
365 boost::trim(new_val);
366 res.push_back(new_val);
367 ++i2;
368 i1=i2;
369 }else{
370 if (part.empty())
371 in_parenthesis = false;
372 ++i2;
373 }
374 continue;
375 }
376 bool found=false;
377 for(std::size_t i=0; i < left.size(); i++){
378 if (*i2 == left[i]){
379 if (!separator && part.empty()){
380 std::string new_val(i1, i2);
381 if (flags & STRIP_SPACES)
382 boost::trim(new_val);
383 res.push_back(new_val);
384 ++i2;
385 i1=i2;
386 }else{
387 ++i2;
388 }
389 part.push_back(right[i]);
390 found=true;
391 break;
392 }
393 }
394 if(!found){
395 ++i2;
396 } else
397 in_parenthesis = true;
398 }
399
400 std::string new_val(i1, i2);
401 if (flags & STRIP_SPACES)
402 boost::trim(new_val);
403 if (!(flags & REMOVE_EMPTY) || !new_val.empty())
404 res.push_back(std::move(new_val));
405
406 if(!part.empty()){
407 ERR_GENERAL << "Mismatched parenthesis:\n"<<val;
408 }
409
410 return res;
411}
412
413// Modify a number by string representing integer difference, or optionally %
414int apply_modifier( const int number, const std::string &amount, const int minimum ) {
415 // wassert( amount.empty() == false );
416 int value = 0;
417 try {
418 value = std::stoi(amount);
419 } catch(const std::invalid_argument&) {}
420 if(amount[amount.size()-1] == '%') {
421 value = div100rounded(number * value);
422 }
423 value += number;
424 if (( minimum > 0 ) && ( value < minimum ))
425 value = minimum;
426 return value;
427}
428
429std::string escape(std::string_view str, const char *special_chars)
430{
431 std::string::size_type pos = str.find_first_of(special_chars);
432 if (pos == std::string::npos) {
433 return std::string(str);
434 }
435 std::string res = std::string(str);
436 do {
437 res.insert(pos, 1, '\\');
438 pos = res.find_first_of(special_chars, pos + 2);
439 } while (pos != std::string::npos);
440 return res;
441}
442
443std::string unescape(std::string_view str)
444{
445 std::string::size_type pos = str.find('\\');
446 if (pos == std::string::npos) {
447 return std::string(str);
448 }
449 std::string res = std::string(str);
450 do {
451 res.erase(pos, 1);
452 pos = res.find('\\', pos + 1);
453 } while (pos != std::string::npos);
454 return res;
455}
456
457std::string urlencode(std::string_view str)
458{
459 static const std::string nonresv_str =
460 "-."
461 "0123456789"
462 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
463 "_"
464 "abcdefghijklmnopqrstuvwxyz"
465 "~";
466 static const std::set<char> nonresv(nonresv_str.begin(), nonresv_str.end());
467
468 std::ostringstream res;
469 res << std::hex;
470 res.fill('0');
471
472 for(char c : str) {
473 if(nonresv.count(c) != 0) {
474 res << c;
475 continue;
476 }
477
478 res << '%';
479 res.width(2);
480 res << static_cast<int>(c);
481 }
482
483 return res.str();
484}
485
486bool string_bool(const std::string& str, bool def) {
487 if (str.empty()) return def;
488
489 // yes/no is the standard, test it first
490 if (str == "yes") return true;
491 if (str == "no"|| str == "false" || str == "off" || str == "0" || str == "0.0")
492 return false;
493
494 // all other non-empty string are considered as true
495 return true;
496}
497
498std::string bool_string(const bool value)
499{
500 std::ostringstream ss;
501 ss << std::boolalpha << value;
502
503 return ss.str();
504}
505
506std::string signed_value(int val)
507{
508 std::ostringstream oss;
509 oss << (val >= 0 ? "+" : font::unicode_minus) << std::abs(val);
510 return oss.str();
511}
512
513std::string half_signed_value(int val)
514{
515 std::ostringstream oss;
516 if (val < 0)
517 oss << font::unicode_minus;
518 oss << std::abs(val);
519 return oss.str();
520}
521
522static void si_string_impl_stream_write(std::stringstream &ss, double input) {
523 std::streamsize oldprec = ss.precision();
524#ifdef _MSC_VER
525 // For MSVC, default mode misbehaves, so we use fixed instead.
526 ss.precision(1);
527 ss << std::fixed
528 << input;
529#else
530 // In default mode, precision sets the number of significant figures.
531
532 // 999.5 and above will render as 1000+, however, only numbers above 1000 will use 4 digits
533 // Rounding everything from 100 up (at which point we stop using decimals anyway) avoids this.
534 if (input >= 100) {
535 input = std::round(input);
536 }
537
538 // When in binary mode, numbers of up to 1023.9999 can be passed
539 // We should render those with 4 digits, instead of as 1e+3.
540 // Input should be an integer number now, but doubles can do strange things, so check the halfway point instead.
541 if (input >= 999.5) {
542 ss.precision(4);
543 } else {
544 ss.precision(3);
545 }
546 ss << input;
547#endif
548 ss.precision(oldprec);
549}
550
551std::string si_string(double input, bool base2, const std::string& unit) {
552 const double multiplier = base2 ? 1024 : 1000;
553
554 typedef std::array<std::string, 9> strings9;
555
556 if(input < 0){
557 return font::unicode_minus + si_string(std::abs(input), base2, unit);
558 }
559
560 strings9 prefixes;
561 strings9::const_iterator prefix;
562 if (input == 0.0) {
563 strings9 tmp { { "","","","","","","","","" } };
564 prefixes = tmp;
565 prefix = prefixes.begin();
566 } else if (input < 1.0) {
567 strings9 tmp { {
568 "",
569 _("prefix_milli^m"),
570 _("prefix_micro^µ"),
571 _("prefix_nano^n"),
572 _("prefix_pico^p"),
573 _("prefix_femto^f"),
574 _("prefix_atto^a"),
575 _("prefix_zepto^z"),
576 _("prefix_yocto^y")
577 } };
578 prefixes = tmp;
579 prefix = prefixes.begin();
580 while (input < 1.0 && *prefix != prefixes.back()) {
581 input *= multiplier;
582 ++prefix;
583 }
584 } else {
585 strings9 tmp { {
586 "",
587 (base2 ?
588 // TRANSLATORS: Translate the K in KiB only
589 _("prefix_kibi^K") :
590 _("prefix_kilo^k")
591 ),
592 _("prefix_mega^M"),
593 _("prefix_giga^G"),
594 _("prefix_tera^T"),
595 _("prefix_peta^P"),
596 _("prefix_exa^E"),
597 _("prefix_zetta^Z"),
598 _("prefix_yotta^Y")
599 } };
600 prefixes = tmp;
601 prefix = prefixes.begin();
602 while (input > multiplier && *prefix != prefixes.back()) {
603 input /= multiplier;
604 ++prefix;
605 }
606 }
607
608 std::stringstream ss;
610 ss << ' '
611 << *prefix
612 // TRANSLATORS: Translate the i in (for example) KiB only
613 << (base2 && (!(*prefix).empty()) ? _("infix_binary^i") : "")
614 << unit;
615 return ss.str();
616}
617
618static bool is_username_char(char c) {
619 return ((c == '_') || (c == '-'));
620}
621
622static bool is_wildcard_char(char c) {
623 return ((c == '?') || (c == '*'));
624}
625
626bool isvalid_username(const std::string& username) {
627 const std::size_t alnum = std::count_if(username.begin(), username.end(), isalnum);
628 const std::size_t valid_char =
629 std::count_if(username.begin(), username.end(), is_username_char);
630 if ((alnum + valid_char != username.size())
631 || valid_char == username.size() || username.empty() )
632 {
633 return false;
634 }
635 return true;
636}
637
638bool isvalid_wildcard(const std::string& username) {
639 const std::size_t alnum = std::count_if(username.begin(), username.end(), isalnum);
640 const std::size_t valid_char =
641 std::count_if(username.begin(), username.end(), is_username_char);
642 const std::size_t wild_char =
643 std::count_if(username.begin(), username.end(), is_wildcard_char);
644 if ((alnum + valid_char + wild_char != username.size())
645 || valid_char == username.size() || username.empty() )
646 {
647 return false;
648 }
649 return true;
650}
651
652
653bool word_completion(std::string& text, std::vector<std::string>& wordlist) {
654 std::vector<std::string> matches;
655 const std::size_t last_space = text.rfind(" ");
656 // If last character is a space return.
657 if (last_space == text.size() -1) {
658 wordlist = matches;
659 return false;
660 }
661
662 bool text_start;
663 std::string semiword;
664 if (last_space == std::string::npos) {
665 text_start = true;
666 semiword = text;
667 } else {
668 text_start = false;
669 semiword.assign(text, last_space + 1, text.size());
670 }
671
672 std::string best_match = semiword;
673 for (std::vector<std::string>::const_iterator word = wordlist.begin();
674 word != wordlist.end(); ++word)
675 {
676 if (word->size() < semiword.size()
677 || !std::equal(semiword.begin(), semiword.end(), word->begin(),
678 [](char a, char b) { return tolower(a) == tolower(b); })) // TODO: is this the right approach?
679 {
680 continue;
681 }
682 if (matches.empty()) {
683 best_match = *word;
684 } else {
685 int j = 0;
686 while (toupper(best_match[j]) == toupper((*word)[j])) j++;
687 if (best_match.begin() + j < best_match.end()) {
688 best_match.erase(best_match.begin() + j, best_match.end());
689 }
690 }
691 matches.push_back(*word);
692 }
693 if(!matches.empty()) {
694 text.replace(last_space + 1, best_match.size(), best_match);
695 }
696 wordlist = matches;
697 return text_start;
698}
699
700static bool is_word_boundary(char c) {
701 return (c == ' ' || c == ',' || c == ':' || c == '\'' || c == '"' || c == '-');
702}
703
704bool word_match(const std::string& message, const std::string& word) {
705 std::size_t first = message.find(word);
706 if (first == std::string::npos) return false;
707 if (first == 0 || is_word_boundary(message[first - 1])) {
708 std::size_t next = first + word.size();
709 if (next == message.size() || is_word_boundary(message[next])) {
710 return true;
711 }
712 }
713 return false;
714}
715
716[[nodiscard]] bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept
717{
718 auto s_first = str.cbegin();
719 auto p_first = pat.cbegin();
720 const auto s_last = str.cend();
721 const auto p_last = pat.cend();
722
723 // First, match the initial characters up to, and including, the first '*'/'+' wildcard.
724 // Matching the first wildcard early allows the `std::equal` to be skipped in some common cases.
725
726 auto is_star_or_plus = [](char x) noexcept { return x == '*' || x == '+'; };
727 auto match_char = [](char s, char p) noexcept { return s == p || p == '?'; };
728
729 const auto first_wild = std::find_if(p_first, p_last, is_star_or_plus);
730
731 if(first_wild == p_last) {
732 return std::equal(s_first, s_last, p_first, p_last, match_char);
733 }
734
735 const auto wild_cat = static_cast<int>(*first_wild == '+');
736
737 if(first_wild != p_first) {
738 const auto n_chars = std::distance(p_first, first_wild);
739
740 if(std::distance(s_first, s_last) < (n_chars + wild_cat)) {
741 return false;
742 }
743
744 std::tie(s_first, p_first) = std::mismatch(s_first, s_first + n_chars, p_first, match_char);
745
746 if(p_first != first_wild) {
747 return false;
748 }
749 }
750
751 if(s_first == s_last) {
752 return std::all_of(p_first, p_last, [](char c) { return c == '*'; });
753 }
754
755 s_first += wild_cat;
756 p_first += 1;
757
758 // Then
759
760 while(true) {
761 const auto next_wild = std::find_if(p_first, p_last, is_star_or_plus);
762
763 if(next_wild == p_last) {
764 return boost::ends_with(std::pair{s_first, s_last}, std::pair{p_first, p_last}, match_char);
765 }
766
767 if(next_wild != p_first) {
768 auto [sub_f, sub_l] = std::default_searcher{p_first, next_wild, match_char}(s_first, s_last);
769
770 if(sub_f == s_last) {
771 return false;
772 }
773
774 s_first = sub_l;
775 p_first = next_wild;
776 }
777
778 auto is_wildcard = [](char x) noexcept { return x == '*' || x == '+' || x == '?'; };
779 const auto next_non_wild = std::find_if_not(p_first, p_last, is_wildcard);
780 const auto required_chars = std::distance(p_first, next_non_wild) - std::count(p_first, next_non_wild, '*');
781
782 if(std::distance(s_first, s_last) < required_chars) {
783 return false;
784 }
785
786 s_first += required_chars;
787 p_first = next_non_wild;
788
789 if(p_first == p_last) {
790 return true;
791 }
792 }
793}
794
795void to_sql_wildcards(std::string& str, bool underscores)
796{
797 std::replace(str.begin(), str.end(), '*', '%');
798 if(underscores)
799 {
800 std::size_t n = 0;
801 while((n = str.find("_", n)) != std::string::npos)
802 {
803 str.replace(n, 1, "\\_");
804 n += 2;
805 }
806 }
807}
808
809std::string indent(const std::string& string, std::size_t indent_size)
810{
811 if(indent_size == 0) {
812 return string;
813 }
814
815 std::string indent(indent_size, ' ');
816
817 if(string.empty()) {
818 return indent;
819 }
820
821 const std::vector<std::string>& lines = split(string, '\x0A', 0);
822 std::string res;
823
824 for(std::size_t lno = 0; lno < lines.size();) {
825 const std::string& line = lines[lno];
826
827 // Lines containing only a carriage return count as empty.
828 if(!line.empty() && line != "\x0D") {
829 res += indent;
830 }
831
832 res += line;
833
834 if(++lno < lines.size()) {
835 res += '\x0A';
836 }
837 }
838
839 return res;
840}
841
842std::vector<std::string> quoted_split(const std::string& val, char c, int flags, char quote)
843{
844 std::vector<std::string> res;
845
846 std::string::const_iterator i1 = val.begin();
847 std::string::const_iterator i2 = val.begin();
848
849 while (i2 != val.end()) {
850 if (*i2 == quote) {
851 // Ignore quoted character
852 ++i2;
853 if (i2 != val.end()) ++i2;
854 } else if (*i2 == c) {
855 std::string new_val(i1, i2);
856 if (flags & STRIP_SPACES)
857 boost::trim(new_val);
858 if (!(flags & REMOVE_EMPTY) || !new_val.empty())
859 res.push_back(std::move(new_val));
860 ++i2;
861 if (flags & STRIP_SPACES) {
862 while(i2 != val.end() && *i2 == ' ')
863 ++i2;
864 }
865
866 i1 = i2;
867 } else {
868 ++i2;
869 }
870 }
871
872 std::string new_val(i1, i2);
873 if (flags & STRIP_SPACES)
874 boost::trim(new_val);
875 if (!(flags & REMOVE_EMPTY) || !new_val.empty())
876 res.push_back(new_val);
877
878 return res;
879}
880
881namespace
882{
883/**
884 * Internal common code for parse_range and parse_range_real.
885 *
886 * If str contains two elements and a separator such as "a-b", returns a and b.
887 * Otherwise, returns the original string and utils::nullopt.
888 */
889std::pair<std::string_view, utils::optional<std::string_view>> parse_range_internal_separator(std::string_view str)
890{
891 // If turning this into a list with additional options, ensure that "-" (if present) is last. Otherwise a
892 // range such as "-2..-1" might be incorrectly split as "-2..", "1".
893 static const auto separator = std::string{"-"};
894
895 // Starting from the second character means that it won't interpret the minus
896 // sign on a negative number as the separator.
897 // No need to check the string length first, as str.find() already does that.
898 auto pos = str.find(separator, 1);
899 auto length = separator.size();
900
901 if(pos != std::string::npos && pos + length < str.size()) {
902 return {str.substr(0, pos), str.substr(pos + length)};
903 }
904
905 return {str, utils::nullopt};
906}
907} // namespace
908
909std::pair<int, int> parse_range(std::string_view str)
910{
911 auto [a, b] = parse_range_internal_separator(str);
912 std::pair<int, int> res{0, 0};
913 try {
914 if(a == "-infinity" && b) {
915 // The "&& b" is so that we treat parse_range("-infinity") the same as parse_range("infinity"),
916 // both of those will report an invalid range.
917 res.first = std::numeric_limits<int>::min();
918 } else {
919 res.first = utils::stoi(a);
920 }
921
922 if(!b) {
923 res.second = res.first;
924 } else if(*b == "infinity") {
925 res.second = std::numeric_limits<int>::max();
926 } else {
927 res.second = utils::stoi(*b);
928 if(res.second < res.first) {
929 res.second = res.first;
930 }
931 }
932 } catch(const std::invalid_argument&) {
933 ERR_GENERAL << "Invalid range: " << str;
934 }
935
936 return res;
937}
938
939std::pair<double, double> parse_range_real(std::string_view str)
940{
941 auto [a, b] = parse_range_internal_separator(str);
942 std::pair<double, double> res{0, 0};
943 try {
944 if(a == "-infinity" && b) {
945 // There's already a static-assert for is_iec559 in random.cpp, so this isn't limiting the architectures
946 // that Wesnoth can run on.
947 static_assert(std::numeric_limits<double>::is_iec559,
948 "Don't know how negative infinity is treated on this architecture");
949 res.first = -std::numeric_limits<double>::infinity();
950 } else {
951 res.first = utils::stod(a);
952 }
953
954 if(!b) {
955 res.second = res.first;
956 } else if(*b == "infinity") {
957 res.second = std::numeric_limits<double>::infinity();
958 } else {
959 res.second = utils::stod(*b);
960 if(res.second < res.first) {
961 res.second = res.first;
962 }
963 }
964 } catch(const std::invalid_argument&) {
965 ERR_GENERAL << "Invalid range: " << str;
966 }
967
968 return res;
969}
970
971std::vector<std::pair<int, int>> parse_ranges_unsigned(const std::string& str)
972{
973 auto to_return = parse_ranges_int(str);
974 if(std::any_of(to_return.begin(), to_return.end(), [](const std::pair<int, int>& r) { return r.first < 0; })) {
975 ERR_GENERAL << "Invalid range (expected values to be zero or positive): " << str;
976 return {};
977 }
978
979 return to_return;
980}
981
982std::vector<std::pair<double, double>> parse_ranges_real(const std::string& str)
983{
984 std::vector<std::pair<double, double>> to_return;
985 for(const std::string& r : utils::split(str)) {
986 to_return.push_back(parse_range_real(r));
987 }
988
989 return to_return;
990}
991
992std::vector<std::pair<int, int>> parse_ranges_int(const std::string& str)
993{
994 std::vector<std::pair<int, int>> to_return;
995 for(const std::string& r : utils::split(str)) {
996 to_return.push_back(parse_range(r));
997 }
998
999 return to_return;
1000}
1001
1002void ellipsis_truncate(std::string& str, const std::size_t size)
1003{
1004 const std::size_t prev_size = str.length();
1005
1006 utf8::truncate(str, size);
1007
1008 if(str.length() != prev_size) {
1009 str += font::ellipsis;
1010 }
1011}
1012
1013} // end namespace utils
This class represents a single unit of a specific type.
Definition unit.hpp:39
static lg::log_domain log_engine("engine")
#define ERR_GENERAL
static std::string _(const char *str)
Definition gettext.hpp:100
static int indent
Definition log.cpp:88
Standard logging facilities (interface).
constexpr int div100rounded(int num)
Guarantees portable results for division by 100; round half up, to the nearest integer.
Definition math.hpp:37
const std::string ellipsis
Definition constants.cpp:39
const std::string unicode_minus
Definition constants.cpp:42
std::string & truncate(std::string &str, const std::size_t size)
Truncates a UTF-8 string to the specified number of characters.
Definition unicode.cpp:118
@ STRIP_SPACES
REMOVE_EMPTY: remove empty elements.
std::vector< std::string_view > split_view(std::string_view s, const char sep, const int flags)
std::string si_string(double input, bool base2, const std::string &unit)
Convert into a string with an SI-postfix.
void trim(std::string_view &s)
std::map< std::string, std::string > map_split(const std::string &val, char major, char minor, int flags, const std::string &default_value)
Splits a string based on two separators into a map.
bool isvalid_wildcard(const std::string &username)
Check if the username pattern contains only valid characters.
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
std::vector< std::string > quoted_split(const std::string &val, char c, int flags, char quote)
This function is identical to split(), except it does not split when it otherwise would if the previo...
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition charconv.hpp:156
static void si_string_impl_stream_write(std::stringstream &ss, double input)
std::vector< std::pair< int, int > > parse_ranges_int(const std::string &str)
Handles a comma-separated list of inputs to parse_range.
std::vector< std::string > parenthetical_split(std::string_view val, const char separator, std::string_view left, std::string_view right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept
Performs pattern matching with wildcards.
std::string half_signed_value(int val)
Sign with Unicode "−" if negative.
std::string bool_string(const bool value)
Converts a bool value to 'true' or 'false'.
static bool is_word_boundary(char c)
void ellipsis_truncate(std::string &str, const std::size_t size)
Truncates a string to a given utf-8 character count and then appends an ellipsis.
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
std::string urlencode(std::string_view str)
Percent-escape characters in a UTF-8 string intended to be part of a URL.
void to_sql_wildcards(std::string &str, bool underscores)
Converts '*' to '' and optionally escapes '_'.
void split_foreach(std::string_view s, char sep, const int flags, const F &f)
bool string_bool(const std::string &str, bool def)
Convert no, false, off, 0, 0.0 to false, empty to def, and others to true.
bool isvalid_username(const std::string &username)
Check if the username contains only valid characters.
double stod(std::string_view str)
Same interface as std::stod and meant as a drop in replacement, except:
Definition charconv.hpp:142
bool portable_isspace(const char c)
int apply_modifier(const int number, const std::string &amount, const int minimum)
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
std::pair< int, int > parse_range(std::string_view str)
Recognises the following patterns, and returns a {min, max} pair.
bool isnewline(const char c)
bool notspace(const char c)
std::string unescape(std::string_view str)
Remove all escape characters (backslash)
std::vector< std::pair< double, double > > parse_ranges_real(const std::string &str)
bool word_match(const std::string &message, const std::string &word)
Check if a message contains a word.
std::string escape(std::string_view str, const char *special_chars)
Prepends a configurable set of characters with a backslash.
std::string signed_value(int val)
Convert into a signed value (using the Unicode "−" and +0 convention.
std::pair< double, double > parse_range_real(std::string_view str)
Recognises similar patterns to parse_range, and returns a {min, max} pair.
std::vector< std::string > split(const config_attribute_value &val)
bool word_completion(std::string &text, std::vector< std::string > &wordlist)
Try to complete the last word of 'text' with the 'wordlist'.
static bool is_wildcard_char(char c)
static bool is_username_char(char c)
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
mock_party p
static map_location::direction n
static map_location::direction s
#define i
#define c
#define b