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