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