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