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