The Battle for Wesnoth  1.15.0-dev
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 - 2018 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 "gettext.hpp"
23 
24 static lg::log_domain log_engine("engine");
25 #define ERR_NG LOG_STREAM(err, log_engine)
26 #define WRN_NG LOG_STREAM(warn, log_engine)
27 
28 static bool two_dots(char a, char b) { return a == '.' && b == '.'; }
29 
30 namespace utils {
31 
32  namespace detail {
33  std::string(* evaluate_formula)(const std::string& formula) = nullptr;
34  }
35 
36 template <typename T>
38 {
39 public:
40  string_map_variable_set(const std::map<std::string,T>& map) : map_(map) {}
41 
42  virtual config::attribute_value get_variable_const(const std::string &key) const
43  {
45  const auto itor = map_.find(key);
46  if (itor != map_.end())
47  val = itor->second;
48  return val;
49  }
50 private:
51  const std::map<std::string,T>& map_;
52 
53 };
54 }
55 
56 static std::string do_interpolation(const std::string &str, const variable_set& set)
57 {
58  std::string res = str;
59  // This needs to be able to store negative numbers to check for the while's condition
60  // (which is only false when the previous '$' was at index 0)
61  int rfind_dollars_sign_from = res.size();
62  while(rfind_dollars_sign_from >= 0) {
63  // Going in a backwards order allows nested variable-retrieval, e.g. in arrays.
64  // For example, "I am $creatures[$i].user_description!"
65  const std::string::size_type var_begin_loc = res.rfind('$', rfind_dollars_sign_from);
66 
67  // If there are no '$' left then we're done.
68  if(var_begin_loc == std::string::npos) {
69  break;
70  }
71 
72  // For the next iteration of the loop, search for more '$'
73  // (not from the same place because sometimes the '$' is not replaced)
74  rfind_dollars_sign_from = static_cast<int>(var_begin_loc) - 1;
75 
76 
77  const std::string::iterator var_begin = res.begin() + var_begin_loc;
78 
79  // The '$' is not part of the variable name.
80  const std::string::iterator var_name_begin = var_begin + 1;
81  std::string::iterator var_end = var_name_begin;
82 
83  if(var_name_begin == res.end()) {
84  // Any '$' at the end of a string is just a '$'
85  continue;
86  } else if(*var_name_begin == '(') {
87  // The $( ... ) syntax invokes a formula
88  int paren_nesting_level = 0;
89  bool in_string = false,
90  in_comment = false;
91  do {
92  switch(*var_end) {
93  case '(':
94  if(!in_string && !in_comment) {
95  ++paren_nesting_level;
96  }
97  break;
98  case ')':
99  if(!in_string && !in_comment) {
100  --paren_nesting_level;
101  }
102  break;
103  case '#':
104  if(!in_string) {
105  in_comment = !in_comment;
106  }
107  break;
108  case '\'':
109  if(!in_comment) {
110  in_string = !in_string;
111  }
112  break;
113  // TODO: support escape sequences when/if they are allowed in FormulaAI strings
114  }
115  } while(++var_end != res.end() && paren_nesting_level > 0);
116  if(utils::detail::evaluate_formula == nullptr) {
117  WRN_NG << "Formula substitution ignored (and removed) because WFL engine is not present in the server.\n";
118  res.replace(var_begin, var_end, "");
119  continue;
120  }
121  if(paren_nesting_level > 0) {
122  ERR_NG << "Formula in WML string cannot be evaluated due to "
123  << "a missing closing parenthesis:\n\t--> \""
124  << std::string(var_begin, var_end) << "\"\n";
125  res.replace(var_begin, var_end, "");
126  continue;
127  }
128  res.replace(var_begin, var_end, utils::detail::evaluate_formula(std::string(var_begin+2, var_end-1)));
129  continue;
130  }
131 
132  // Find the maximum extent of the variable name (it may be shortened later).
133  for(int bracket_nesting_level = 0; var_end != res.end(); ++var_end) {
134  const char c = *var_end;
135  if(c == '[') {
136  ++bracket_nesting_level;
137  }
138  else if(c == ']') {
139  if(--bracket_nesting_level < 0) {
140  break;
141  }
142  }
143  // isascii() breaks on mingw with -std=c++0x
144  else if (!(((c) & ~0x7f) == 0)/*isascii(c)*/ || (!isalnum(c) && c != '.' && c != '_')) {
145  break;
146  }
147  }
148 
149  // Two dots in a row cannot be part of a valid variable name.
150  // That matters for random=, e.g. $x..$y
151  var_end = std::adjacent_find(var_name_begin, var_end, two_dots);
152  /// the default value is specified after ''?'
153  const std::string::iterator default_start = var_end < res.end() && *var_end == '?' ? var_end + 1 : res.end();
154 
155  // If the last character is '.', then it can't be a sub-variable.
156  // It's probably meant to be a period instead. Don't include it.
157  // Would need to do it repetitively if there are multiple '.'s at the end,
158  // but don't actually need to do so because the previous check for adjacent '.'s would catch that.
159  // For example, "My score is $score." or "My score is $score..."
160  if(*(var_end-1) == '.'
161  // However, "$array[$i]" by itself does not name a variable,
162  // so if "$array[$i]." is encountered, then best to include the '.',
163  // so that it more closely follows the syntax of a variable (if only to get rid of all of it).
164  // (If it's the script writer's error, they'll have to fix it in either case.)
165  // For example in "$array[$i].$field_name", if field_name does not exist as a variable,
166  // then the result of the expansion should be "", not "." (which it would be if this exception did not exist).
167  && *(var_end-2) != ']') {
168  --var_end;
169  }
170 
171  const std::string var_name(var_name_begin, var_end);
172  if(default_start == res.end()) {
173  if(var_end != res.end() && *var_end == '|') {
174  // It's been used to end this variable name; now it has no more effect.
175  // This can allow use of things like "$$composite_var_name|.x"
176  // (Yes, that's a WML 'pointer' of sorts. They are sometimes useful.)
177  // If there should still be a '|' there afterwards to affect other variable names (unlikely),
178  // just put another '|' there, one matching each '$', e.g. "$$var_containing_var_name||blah"
179  ++var_end;
180  }
181 
182 
183  if (var_name.empty()) {
184  // Allow for a way to have $s in a string.
185  // $| will be replaced by $.
186  res.replace(var_begin, var_end, "$");
187  }
188  else {
189  // The variable is replaced with its value.
190  res.replace(var_begin, var_end,
191  set.get_variable_const(var_name));
192  }
193  }
194  else {
195  var_end = default_start;
196  while(var_end != res.end() && *var_end != '|') {
197  ++var_end;
198  }
199  const std::string::iterator default_end = var_end;
200  const config::attribute_value& val = set.get_variable_const(var_name);
201  if(var_end == res.end()) {
202  res.replace(var_begin, default_start - 1, val);
203  }
204  else if(!val.empty()) {
205  res.replace(var_begin, var_end + 1, val);
206  }
207  else {
208  res.replace(var_begin, var_end + 1, std::string(default_start, default_end));
209  }
210  }
211  }
212 
213  return res;
214 }
215 
216 namespace utils {
217 
218 std::string interpolate_variables_into_string(const std::string &str, const string_map * const symbols)
219 {
220  auto set = string_map_variable_set<t_string>(*symbols);
221  return do_interpolation(str, set);
222 }
223 
224 std::string interpolate_variables_into_string(const std::string &str, const std::map<std::string,std::string> * const symbols)
225 {
226  auto set = string_map_variable_set<std::string>(*symbols);
227  return do_interpolation(str, set);
228 }
229 
230 std::string interpolate_variables_into_string(const std::string &str, const variable_set& variables)
231 {
232  return do_interpolation(str, variables);
233 }
234 
236 {
237  if(!tstr.str().empty()) {
238  std::string interp = utils::interpolate_variables_into_string(tstr.str(), variables);
239  if(tstr.str() != interp) {
240  return t_string(interp);
241  }
242  }
243  return tstr;
244 }
245 
246 std::string format_conjunct_list(const t_string& empty, const std::vector<t_string>& elems) {
247  switch(elems.size()) {
248  case 0: return empty;
249  case 1: return elems[0];
250  // TRANSLATORS: Formats a two-element conjunctive list.
251  case 2: return VGETTEXT("conjunct pair^$first and $second", {{"first", elems[0]}, {"second", elems[1]}});
252  }
253  // TRANSLATORS: Formats the first two elements of a conjunctive list.
254  std::string prefix = VGETTEXT("conjunct start^$first, $second", {{"first", elems[0]}, {"second", elems[1]}});
255  // For size=3 this loop is not entered
256  for(std::size_t i = 2; i < elems.size() - 1; i++) {
257  // TRANSLATORS: Formats successive elements of a conjunctive list.
258  prefix = VGETTEXT("conjunct mid^$prefix, $next", {{"prefix", prefix}, {"next", elems[i]}});
259  }
260  // TRANSLATORS: Formats the final element of a conjunctive list.
261  return VGETTEXT("conjunct end^$prefix, and $last", {{"prefix", prefix}, {"last", elems.back()}});
262 }
263 
264 std::string format_disjunct_list(const t_string& empty, const std::vector<t_string>& elems) {
265  switch(elems.size()) {
266  case 0: return empty;
267  case 1: return elems[0];
268  // TRANSLATORS: Formats a two-element disjunctive list.
269  case 2: return VGETTEXT("disjunct pair^$first or $second", {{"first", elems[0]}, {"second", elems[1]}});
270  }
271  // TRANSLATORS: Formats the first two elements of a disjunctive list.
272  std::string prefix = VGETTEXT("disjunct start^$first, $second", {{"first", elems[0]}, {"second", elems[1]}});
273  // For size=3 this loop is not entered
274  for(std::size_t i = 2; i < elems.size() - 1; i++) {
275  // TRANSLATORS: Formats successive elements of a disjunctive list.
276  prefix = VGETTEXT("disjunct mid^$prefix, $next", {{"prefix", prefix}, {"next", elems[i]}});
277  }
278  // TRANSLATORS: Formats the final element of a disjunctive list.
279  return VGETTEXT("disjunct end^$prefix, or $last", {{"prefix", prefix}, {"last", elems.back()}});
280 }
281 
282 }
283 
284 std::string vgettext(const char *msgid, const utils::string_map& symbols)
285 {
286  const std::string orig(_(msgid));
287  const std::string msg = utils::interpolate_variables_into_string(orig, &symbols);
288  return msg;
289 }
290 
291 std::string vgettext(const char *domain
292  , const char *msgid
293  , const utils::string_map& symbols)
294 {
295  const std::string orig(translation::dsgettext(domain, msgid));
296  const std::string msg = utils::interpolate_variables_into_string(orig, &symbols);
297  return msg;
298 }
299 std::string vngettext(const char* sing, const char* plur, int n, const utils::string_map& symbols)
300 {
301  const std::string orig(_n(sing, plur, n));
302  const std::string msg = utils::interpolate_variables_into_string(orig, &symbols);
303  return msg;
304 }
305 
306 std::string vngettext(const char *domain, const char *sing, const char* plur, int n, const utils::string_map& symbols)
307 {
308  const std::string orig(translation::dsngettext(domain, sing, plur, n));
309  const std::string msg = utils::interpolate_variables_into_string(orig, &symbols);
310  return msg;
311 }
bool empty() const
Tests for an attribute that either was never set or was set to "".
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
#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 &#39;$&#39; in the string &#39;str&#39; with the equivalent ...
std::map< std::string, t_string > string_map
Variant for storing WML attributes.
#define a
std::string dsngettext(const char *domainname, const char *singular, const char *plural, int n)
Definition: gettext.cpp:417
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:109
#define WRN_NG
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)
Definition: gettext.cpp:404
#define b
virtual config::attribute_value get_variable_const(const std::string &key) const
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
static std::string do_interpolation(const std::string &str, const variable_set &set)
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)
std::string(* evaluate_formula)(const std::string &formula)
static bool two_dots(char a, char b)
std::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)
Standard logging facilities (interface).
std::string vngettext(const char *sing, const char *plur, int n, const utils::string_map &symbols)
mock_char c
const std::string & str() const
Definition: tstring.hpp:186
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