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