The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
string_utils.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 by David White <dave@whitevine.net>
3  Copyright (C) 2005 - 2017 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "formula/string_utils.hpp"
19 
20 #include "config.hpp"
21 #include "log.hpp"
22 #include "formula/formula.hpp"
23 #include "gettext.hpp"
24 
25 static lg::log_domain log_engine("engine");
26 #define ERR_NG LOG_STREAM(err, log_engine)
27 
28 static bool two_dots(char a, char b) { return a == '.' && b == '.'; }
29 
30 namespace utils {
31 
32 template <typename T>
34 {
35 public:
36  string_map_variable_set(const std::map<std::string,T>& map) : map_(map) {}
37 
39  {
41  const auto itor = map_.find(key);
42  if (itor != map_.end())
43  val = itor->second;
44  return val;
45  }
46 private:
47  const std::map<std::string,T>& map_;
48 
49 };
50 }
51 
53 {
54  std::string res = str;
55  // This needs to be able to store negative numbers to check for the while's condition
56  // (which is only false when the previous '$' was at index 0)
57  int rfind_dollars_sign_from = res.size();
58  while(rfind_dollars_sign_from >= 0) {
59  // Going in a backwards order allows nested variable-retrieval, e.g. in arrays.
60  // For example, "I am $creatures[$i].user_description!"
61  const std::string::size_type var_begin_loc = res.rfind('$', rfind_dollars_sign_from);
62 
63  // If there are no '$' left then we're done.
64  if(var_begin_loc == std::string::npos) {
65  break;
66  }
67 
68  // For the next iteration of the loop, search for more '$'
69  // (not from the same place because sometimes the '$' is not replaced)
70  rfind_dollars_sign_from = int(var_begin_loc) - 1;
71 
72 
73  const std::string::iterator var_begin = res.begin() + var_begin_loc;
74 
75  // The '$' is not part of the variable name.
76  const std::string::iterator var_name_begin = var_begin + 1;
77  std::string::iterator var_end = var_name_begin;
78 
79  if(var_name_begin == res.end()) {
80  // Any '$' at the end of a string is just a '$'
81  continue;
82  } else if(*var_name_begin == '(') {
83  // The $( ... ) syntax invokes a formula
84  int paren_nesting_level = 0;
85  bool in_string = false,
86  in_comment = false;
87  do {
88  switch(*var_end) {
89  case '(':
90  if(!in_string && !in_comment) {
91  ++paren_nesting_level;
92  }
93  break;
94  case ')':
95  if(!in_string && !in_comment) {
96  --paren_nesting_level;
97  }
98  break;
99  case '#':
100  if(!in_string) {
101  in_comment = !in_comment;
102  }
103  break;
104  case '\'':
105  if(!in_comment) {
106  in_string = !in_string;
107  }
108  break;
109  // TODO: support escape sequences when/if they are allowed in FormulaAI strings
110  }
111  } while(++var_end != res.end() && paren_nesting_level > 0);
112  if(paren_nesting_level > 0) {
113  ERR_NG << "Formula in WML string cannot be evaluated due to "
114  << "a missing closing parenthesis:\n\t--> \""
115  << std::string(var_begin, var_end) << "\"\n";
116  res.replace(var_begin, var_end, "");
117  continue;
118  }
119  try {
120  const wfl::formula form(std::string(var_begin+2, var_end-1));
121  res.replace(var_begin, var_end, form.evaluate().string_cast());
122  } catch(wfl::formula_error& e) {
123  ERR_NG << "Formula in WML string cannot be evaluated due to "
124  << e.type << "\n\t--> \""
125  << e.formula << "\"\n";
126  res.replace(var_begin, var_end, "");
127  }
128  continue;
129  }
130 
131  // Find the maximum extent of the variable name (it may be shortened later).
132  for(int bracket_nesting_level = 0; var_end != res.end(); ++var_end) {
133  const char c = *var_end;
134  if(c == '[') {
135  ++bracket_nesting_level;
136  }
137  else if(c == ']') {
138  if(--bracket_nesting_level < 0) {
139  break;
140  }
141  }
142  // isascii() breaks on mingw with -std=c++0x
143  else if (!(((c) & ~0x7f) == 0)/*isascii(c)*/ || (!isalnum(c) && c != '.' && c != '_')) {
144  break;
145  }
146  }
147 
148  // Two dots in a row cannot be part of a valid variable name.
149  // That matters for random=, e.g. $x..$y
150  var_end = std::adjacent_find(var_name_begin, var_end, two_dots);
151  /// the default value is specified after ''?'
152  const std::string::iterator default_start = var_end < res.end() && *var_end == '?' ? var_end + 1 : res.end();
153 
154  // If the last character is '.', then it can't be a sub-variable.
155  // It's probably meant to be a period instead. Don't include it.
156  // Would need to do it repetitively if there are multiple '.'s at the end,
157  // but don't actually need to do so because the previous check for adjacent '.'s would catch that.
158  // For example, "My score is $score." or "My score is $score..."
159  if(*(var_end-1) == '.'
160  // However, "$array[$i]" by itself does not name a variable,
161  // so if "$array[$i]." is encountered, then best to include the '.',
162  // so that it more closely follows the syntax of a variable (if only to get rid of all of it).
163  // (If it's the script writer's error, they'll have to fix it in either case.)
164  // For example in "$array[$i].$field_name", if field_name does not exist as a variable,
165  // then the result of the expansion should be "", not "." (which it would be if this exception did not exist).
166  && *(var_end-2) != ']') {
167  --var_end;
168  }
169 
170  const std::string var_name(var_name_begin, var_end);
171  if(default_start == res.end()) {
172  if(var_end != res.end() && *var_end == '|') {
173  // It's been used to end this variable name; now it has no more effect.
174  // This can allow use of things like "$$composite_var_name|.x"
175  // (Yes, that's a WML 'pointer' of sorts. They are sometimes useful.)
176  // If there should still be a '|' there afterwards to affect other variable names (unlikely),
177  // just put another '|' there, one matching each '$', e.g. "$$var_containing_var_name||blah"
178  ++var_end;
179  }
180 
181 
182  if (var_name.empty()) {
183  // Allow for a way to have $s in a string.
184  // $| will be replaced by $.
185  res.replace(var_begin, var_end, "$");
186  }
187  else {
188  // The variable is replaced with its value.
189  res.replace(var_begin, var_end,
190  set.get_variable_const(var_name));
191  }
192  }
193  else {
194  var_end = default_start;
195  while(var_end != res.end() && *var_end != '|') {
196  ++var_end;
197  }
198  const std::string::iterator default_end = var_end;
199  const config::attribute_value& val = set.get_variable_const(var_name);
200  if(var_end == res.end()) {
201  res.replace(var_begin, default_start - 1, val);
202  }
203  else if(!val.empty()) {
204  res.replace(var_begin, var_end + 1, val);
205  }
206  else {
207  res.replace(var_begin, var_end + 1, std::string(default_start, default_end));
208  }
209  }
210  }
211 
212  return res;
213 }
214 
215 namespace utils {
216 
218 {
219  auto set = string_map_variable_set<t_string>(*symbols);
220  return do_interpolation(str, set);
221 }
222 
223 std::string interpolate_variables_into_string(const std::string &str, const std::map<std::string,std::string> * const symbols)
224 {
225  auto set = string_map_variable_set<std::string>(*symbols);
226  return do_interpolation(str, set);
227 }
228 
230 {
231  return do_interpolation(str, variables);
232 }
233 
235 {
236  if(!tstr.str().empty()) {
237  std::string interp = utils::interpolate_variables_into_string(tstr.str(), variables);
238  if(tstr.str() != interp) {
239  return t_string(interp);
240  }
241  }
242  return tstr;
243 }
244 
245 std::string format_conjunct_list(const t_string& empty, const std::vector<t_string>& elems) {
246  switch(elems.size()) {
247  case 0: return empty;
248  case 1: return elems[0];
249  // TRANSLATORS: Formats a two-element conjunctive list.
250  case 2: return VGETTEXT("conjunct pair^$first and $second", {{"first", elems[0]}, {"second", elems[1]}});
251  }
252  // TRANSLATORS: Formats the first two elements of a conjunctive list.
253  std::string prefix = VGETTEXT("conjunct start^$first, $second", {{"first", elems[0]}, {"second", elems[1]}});
254  // For size=3 this loop is not entered
255  for(size_t i = 2; i < elems.size() - 1; i++) {
256  // TRANSLATORS: Formats successive elements of a conjunctive list.
257  prefix = VGETTEXT("conjunct mid^$prefix, $next", {{"prefix", prefix}, {"next", elems[i]}});
258  }
259  // TRANSLATORS: Formats the final element of a conjunctive list.
260  return VGETTEXT("conjunct end^$prefix, and $last", {{"prefix", prefix}, {"last", elems.back()}});
261 }
262 
263 std::string format_disjunct_list(const t_string& empty, const std::vector<t_string>& elems) {
264  switch(elems.size()) {
265  case 0: return empty;
266  case 1: return elems[0];
267  // TRANSLATORS: Formats a two-element disjunctive list.
268  case 2: return VGETTEXT("disjunct pair^$first or $second", {{"first", elems[0]}, {"second", elems[1]}});
269  }
270  // TRANSLATORS: Formats the first two elements of a disjunctive list.
271  std::string prefix = VGETTEXT("disjunct start^$first, $second", {{"first", elems[0]}, {"second", elems[1]}});
272  // For size=3 this loop is not entered
273  for(size_t i = 2; i < elems.size() - 1; i++) {
274  // TRANSLATORS: Formats successive elements of a disjunctive list.
275  prefix = VGETTEXT("disjunct mid^$prefix, $next", {{"prefix", prefix}, {"next", elems[i]}});
276  }
277  // TRANSLATORS: Formats the final element of a disjunctive list.
278  return VGETTEXT("disjunct end^$prefix, or $last", {{"prefix", prefix}, {"last", elems.back()}});
279 }
280 
281 }
282 
283 std::string vgettext(const char *msgid, const utils::string_map& symbols)
284 {
285  const std::string orig(_(msgid));
287  return msg;
288 }
289 
290 std::string vgettext(const char *domain
291  , const char *msgid
292  , const utils::string_map& symbols)
293 {
294  const std::string orig(translation::dsgettext(domain, msgid));
296  return msg;
297 }
298 std::string vngettext(const char* sing, const char* plur, int n, const utils::string_map& symbols)
299 {
300  const std::string orig(_n(sing, plur, n));
302  return msg;
303 }
304 
305 std::string vngettext(const char *domain, const char *sing, const char* plur, int n, const utils::string_map& symbols)
306 {
307  const std::string orig(translation::dsngettext(domain, sing, plur, n));
309  return msg;
310 }
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::vector< char_t > string
#define ERR_NG
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
std::map< std::string, t_string > string_map
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:154
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:39
std::string formula
Definition: formula.hpp:106
virtual config::attribute_value get_variable_const(const std::string &key) const
Variant for storing WML attributes.
#define a
bool empty() const
Tests for an attribute that either was never set or was set to "".
std::string dsngettext(const char *domainname, const char *singular, const char *plural, int n)
std::string format_disjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a disjunctive list.
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
Definitions for the interface to Wesnoth Markup Language (WML).
const std::map< std::string, T > & map_
std::string dsgettext(const char *domainname, const char *msgid)
#define b
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
static std::string do_interpolation(const std::string &str, const variable_set &set)
std::string type
Definition: formula.hpp:105
static UNUSEDNOWARN std::string _n(const char *str1, const char *str2, int n)
Definition: gettext.hpp:93
string_map_variable_set(const std::map< std::string, T > &map)
static bool two_dots(char a, char b)
std::string string_cast() const
Definition: variant.cpp:644
size_t i
Definition: function.cpp:933
#define VGETTEXT(msgid,...)
static lg::log_domain log_engine("engine")
std::string vgettext(const char *msgid, const utils::string_map &symbols)
virtual config::attribute_value get_variable_const(const std::string &id) const =0
Standard logging facilities (interface).
std::string vngettext(const char *sing, const char *plur, int n, const utils::string_map &symbols)
#define e
const std::string & str() const
Definition: tstring.hpp:186
mock_char c
t_string interpolate_variables_into_tstring(const t_string &tstr, const variable_set &variables)
Function that does the same as the above, for t_stringS.
static map_location::DIRECTION n
std::string::const_iterator iterator
Definition: tokenizer.hpp:24