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 #pragma once
17 
18 #include "font/constants.hpp"
19 #include "serialization/string_view.hpp"
20 
21 #include <algorithm>
22 #include <map>
23 #include <ostream>
24 #include <set>
25 #include <sstream>
26 #include <string>
27 #include <utility>
28 #include <vector>
29 
30 class t_string;
31 
32 namespace utils {
33 
34 using string_map = std::map<std::string, t_string>;
35 
36 bool isnewline(const char c);
37 bool portable_isspace(const char c);
38 bool notspace(char c);
39 
40 enum {
41 	REMOVE_EMPTY = 0x01, /** REMOVE_EMPTY: remove empty elements. */
42 	STRIP_SPACES = 0x02  /** STRIP_SPACES: strips leading and trailing blank spaces. */
43 };
44 
45 /** Splits a (comma-)separated string into a vector of pieces. */
46 std::vector<std::string> split(const std::string& val, const char c = ',', const int flags = REMOVE_EMPTY | STRIP_SPACES);
47 
48 /**
49  * This function is identical to split(), except it does not split when it otherwise would if the
50  * previous character was identical to the parameter 'quote' (i.e. it does not split quoted commas).
51  * This method was added to make it possible to quote user input, particularly so commas in user input
52  * would not cause visual problems in menus.
53  *
54  * @todo Why not change split()? That would change the methods post condition.
55  */
56 std::vector<std::string> quoted_split(const std::string& val, char c= ',', int flags = REMOVE_EMPTY | STRIP_SPACES, char quote = '\\');
57 
58 /**
59  * Splits a (comma-)separated string into a set of pieces.
60  * See split() for the meanings of the parameters.
61  */
set_split(const std::string & val,const char c=',',const int flags=REMOVE_EMPTY|STRIP_SPACES)62 inline std::set<std::string> set_split(const std::string& val, const char c = ',', const int flags = REMOVE_EMPTY | STRIP_SPACES)
63 {
64 	std::vector<std::string> vec_split = split(val, c, flags);
65 	return std::set< std::string >(vec_split.begin(), vec_split.end());
66 }
67 
68 /**
69  * Splits a string based on two separators into a map.
70  *
71  * Major: the separator between elements of the map
72  * Minor: the separator between keys and values in one element
73  *
74  * For example, the string 'a:b,c:d,e:f' would be parsed into:
75  *  a => b
76  *  c => d
77  *  e => f
78  */
79 std::map<std::string, std::string> map_split(
80 	const std::string& val,
81 	char major = ',',
82 	char minor = ':',
83 	int flags = REMOVE_EMPTY | STRIP_SPACES,
84 	const std::string& default_value = "");
85 
86 /**
87  * Splits a string based either on a separator, except then the text appears within specified parenthesis.
88  *
89  * If the separator is "0" (default), it splits a string into an odd number of parts:
90  * - The part before the first '(',
91  * - the part between the first '('
92  * - and the matching right ')', etc ...
93  * and the remainder of the string.
94  *
95  * Note that one can use the same character for both the left and right parenthesis, which usually makes
96  * the most sense for this function.
97  *
98  * Note that this will find the first matching char in the left string and match against the corresponding
99  * char in the right string. A correctly processed string should return a vector with an odd number of
100  * elements. Empty elements are never removed as they are placeholders, hence REMOVE EMPTY only works for
101  * the separator split.
102  *
103  * INPUT:   ("a(b)c{d}e(f{g})h", 0, "({", ")}")
104  * RETURNS: {"a", "b", "c", "d", "e", "f{g}", "h"}
105  */
106 std::vector< std::string > parenthetical_split(
107 	const std::string& val,
108 	const char separator = 0,
109 	const std::string& left = "(",
110 	const std::string& right = ")",
111 	const int flags = REMOVE_EMPTY | STRIP_SPACES);
112 
113 /**
114  * Similar to parenthetical_split, but also expands embedded square brackets.
115  *
116  * Notes:
117  * - The Separator must be specified and number of entries in each square bracket must match in each section.
118  * - Leading zeros are preserved if specified between square brackets.
119  * - An asterisk as in [a*n] indicates to expand 'a' n times
120  *
121  * This is useful for expanding animation WML code.
122  *
123  * Examples:
124  *
125  * INPUT:   ("a[1-3](1,[5,6,7]),b[8,9]", ",")
126  * RETURNS: {"a1(1,5)", "a2(1,6)", "a3(1,7)", "b8", "b9"}
127  *
128  * INPUT:   ("abc[07-10]")
129  * RETURNS: {"abc07", "abc08", "abc09", "abc10"}
130  *
131  * INPUT:   ("a[1,2]b[3-4]:c[5,6]")
132  * RETURNS: {"a1b3:c5", "a2b4:c6"}
133  *
134  * INPUT:   ("abc[3,1].png")
135  * RETURNS: {"abc3.png", "abc2.png", "abc1.png"}
136  *
137  * INPUT:   ("abc[de,xyz]")
138  * RETURNS: {"abcde", "abcxyz"}
139  *
140  * INPUT:   ("abc[1*3]")
141  * RETURNS: {"abc1", "abc1", "abc1"}
142  */
143 std::vector<std::string> square_parenthetical_split(
144 	const std::string& val,
145 	const char separator = ',',
146 	const std::string& left = "([",
147 	const std::string& right = ")]",
148 	const int flags = REMOVE_EMPTY | STRIP_SPACES);
149 
150 /**
151  * Splits a string into two parts as evenly as possible based on lines.
152  * For example, if the string contains 3288 lines, then both parts will
153  * be 1644 lines long.
154  *
155  * The line separator in between won't be in either of the parts the
156  * function returns.
157  *
158  * Because this function is intended for extremely long strings
159  * (kilobytes long), it returns string_views for performance.
160  */
161 std::pair<string_view, string_view> vertical_split(const std::string& val);
162 
163 /**
164  * Generates a new string joining container items in a list.
165  *
166  * @param v A container with elements.
167  * @param s List delimiter.
168  */
169 template <typename T>
join(const T & v,const std::string & s=",")170 std::string join(const T& v, const std::string& s = ",")
171 {
172 	std::stringstream str;
173 
174 	for(typename T::const_iterator i = v.begin(); i != v.end(); ++i) {
175 		str << *i;
176 		if(std::next(i) != v.end()) {
177 			str << s;
178 		}
179 	}
180 
181 	return str.str();
182 }
183 
184 template <typename T>
join_map(const T & v,const std::string & major=",",const std::string & minor=":")185 std::string join_map(
186 	const T& v,
187 	const std::string& major = ",",
188 	const std::string& minor = ":")
189 {
190 	std::stringstream str;
191 
192 	for(typename T::const_iterator i = v.begin(); i != v.end(); ++i) {
193 		str << i->first << minor << i->second;
194 		if(std::next(i) != v.end()) {
195 			str << major;
196 		}
197 	}
198 
199 	return str.str();
200 }
201 
202 /**
203  * Generates a new string containing a bullet list.
204  *
205  * List items are preceded by the indentation blanks, a bullet string and
206  * another blank; all but the last item are followed by a newline.
207  *
208  * @param v A container with elements.
209  * @param indent Number of indentation blanks.
210  * @param bullet The leading bullet string.
211  */
212 template<typename T>
bullet_list(const T & v,size_t indent=4,const std::string & bullet=font::unicode_bullet)213 std::string bullet_list(const T& v, size_t indent = 4, const std::string& bullet = font::unicode_bullet)
214 {
215 	std::ostringstream str;
216 
217 	for(typename T::const_iterator i = v.begin(); i != v.end(); ++i) {
218 		if(i != v.begin()) {
219 			str << '\n';
220 		}
221 
222 		str << std::string(indent, ' ') << bullet << ' ' << *i;
223 	}
224 
225 	return str.str();
226 }
227 
228 /**
229  * Indent a block of text.
230  *
231  * Only lines with content are changed; empty lines are left intact. However,
232  * if @a string is an empty string itself, the indentation unit with the
233  * specified @a indent_size will be returned instead.
234  *
235  * @param string      Text to indent.
236  * @param indent_size Number of indentation units to use.
237  */
238 std::string indent(const std::string& string, size_t indent_size = 4);
239 
240 std::pair<int, int> parse_range(const std::string& str);
241 
242 std::vector<std::pair<int, int>> parse_ranges(const std::string& str);
243 
244 int apply_modifier(const int number, const std::string &amount, const int minimum = 0);
245 
246 /** Add a "+" or replace the "-" par Unicode minus */
print_modifier(const std::string & mod)247 inline std::string print_modifier(const std::string &mod)
248 {
249 	return mod[0] == '-' ? (font::unicode_minus + std::string(mod.begin() + 1, mod.end())) : ("+" + mod);
250 }
251 
252 /** Prepends a configurable set of characters with a backslash */
253 std::string escape(const std::string &str, const char *special_chars);
254 
255 /**
256  * Prepend all special characters with a backslash.
257  *
258  * Special characters are:
259  * #@{}+-,\*=
260  */
escape(const std::string & str)261 inline std::string escape(const std::string &str)
262 {
263 	return escape(str, "#@{}+-,\\*=");
264 }
265 
266 /** Remove all escape characters (backslash) */
267 std::string unescape(const std::string &str);
268 
269 /** Percent-escape characters in a UTF-8 string intended to be part of a URL. */
270 std::string urlencode(const std::string &str);
271 
272 /** Surround the string 'str' with double quotes. */
quote(const std::string & str)273 inline std::string quote(const std::string &str)
274 {
275 	return '"' + str + '"';
276 }
277 
278 /** Convert no, false, off, 0, 0.0 to false, empty to def, and others to true */
279 bool string_bool(const std::string& str,bool def=false);
280 
281 /** Converts a bool value to 'true' or 'false' */
282 std::string bool_string(const bool value);
283 
284 /** Convert into a signed value (using the Unicode "−" and +0 convention */
285 std::string signed_value(int val);
286 
287 /** Sign with Unicode "−" if negative */
288 std::string half_signed_value(int val);
289 
290 /** Convert into a percentage (using the Unicode "−" and +0% convention */
signed_percent(int val)291 inline std::string signed_percent(int val) {return signed_value(val) + "%";}
292 
293 /**
294  * Convert into a string with an SI-postfix.
295  *
296  * If the unit is to be translatable,
297  * a t_string should be passed as the third argument.
298  * _("unit_byte^B") is suggested as standard.
299  *
300  * There are no default values because they would not be translatable.
301  */
302 std::string si_string(double input, bool base2, const std::string& unit);
303 
304 /**
305  * Try to complete the last word of 'text' with the 'wordlist'.
306  *
307  * @param[in, out] text  The parameter's usage is:
308  *                       - Input: Text where we try to complete the last word
309  *                         of.
310  *                       - Output: Text with completed last word.
311  * @param[in, out] wordlist
312  *                        The parameter's usage is:
313  *                        - Inout: A vector of strings to complete against.
314  *                        - Output: A vector of strings that matched 'text'.
315  *
316  * @retval true           iff text is just one word (no spaces)
317  */
318 bool word_completion(std::string& text, std::vector<std::string>& wordlist);
319 
320 /** Check if a message contains a word. */
321 bool word_match(const std::string& message, const std::string& word);
322 
323 /**
324  * Match using '*' as any number of characters (including none),
325  * '+' as one or more characters, and '?' as any one character.
326  */
327 bool wildcard_string_match(const std::string& str, const std::string& match);
328 
329 /**
330  * Check if the username contains only valid characters.
331  *
332  * (all alpha-numeric characters plus underscore and hyphen)
333  */
334 bool isvalid_username(const std::string& login);
335 
336 /**
337  * Check if the username pattern contains only valid characters.
338  *
339  * (all alpha-numeric characters plus underscore, hyphen,
340  * question mark and asterisk)
341  */
342 bool isvalid_wildcard(const std::string& login);
343 
344 /**
345  * Truncates a string to a given utf-8 character count and then appends an ellipsis.
346  */
347 void ellipsis_truncate(std::string& str, const size_t size);
348 
349 } // end namespace utils
350