The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
string_utils.hpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2005 - 2025
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#pragma once
19
20#include "font/constants.hpp"
21
22#include <algorithm>
23#include <map>
24#include <set>
25#include <sstream>
26#include <string>
27#include <string_view>
28#include <utility>
29#include <vector>
30
31class t_string;
32
33namespace utils {
34
35using string_map = std::map<std::string, t_string>;
36
37const std::vector<std::string> res_order = {"blade", "pierce", "impact", "fire", "cold", "arcane"};
38
40 /** Returns whether a < b, considering res_order. */
41 bool operator()(const std::string& a, const std::string& b) const {
42 for(const std::string& r : res_order) {
43 if (b == r) // this means b <= a, so a < b is false
44 return false;
45 if (a == r)
46 return true;
47 }
48 return a < b; // fallback only reached when neither a nor b occur in res_order
49 }
50};
51
52using string_map_res = std::map<std::string, t_string, res_compare>;
53
54bool isnewline(const char c);
55bool portable_isspace(const char c);
56bool notspace(char c);
57
58enum {
59 REMOVE_EMPTY = 0x01, /** REMOVE_EMPTY: remove empty elements. */
60 STRIP_SPACES = 0x02 /** STRIP_SPACES: strips leading and trailing blank spaces. */
61};
62
63void trim(std::string_view& s);
64
65template<typename F>
66void split_foreach_impl(std::string_view s, char sep, const F& f)
67{
68 if(s.empty()) {
69 return;
70 }
71 while(true)
72 {
73 std::size_t partend = s.find(sep);
74 if(partend == std::string_view::npos) {
75 break;
76 }
77 f(s.substr(0, partend));
78 s.remove_prefix(partend + 1);
79 }
80 f(s);
81}
82
83template<typename F>
84void split_foreach(std::string_view s, char sep, const int flags, const F& f)
85{
86 split_foreach_impl(s, sep, [&](std::string_view item) {
87 if(flags & STRIP_SPACES) {
88 trim(item);
89 }
90 if(!(flags & REMOVE_EMPTY) || !item.empty()) {
91 f(item);
92 }
93 });
94}
95
96/** Splits a (comma-)separated string into a vector of pieces. */
97std::vector<std::string> split(std::string_view val, const char c = ',', const int flags = REMOVE_EMPTY | STRIP_SPACES);
98std::set<std::string> split_set(std::string_view val, const char c = ',', const int flags = REMOVE_EMPTY | STRIP_SPACES);
99
100std::vector<std::string_view> split_view(std::string_view val, const char c = ',', const int flags = REMOVE_EMPTY | STRIP_SPACES);
101
102/** You cannot take a non-owning view to a temporary string! */
103std::vector<std::string_view> split_view(std::string&&) = delete;
104
105/**
106 * This function is identical to split(), except it does not split when it otherwise would if the
107 * previous character was identical to the parameter 'quote' (i.e. it does not split quoted commas).
108 * This method was added to make it possible to quote user input, particularly so commas in user input
109 * would not cause visual problems in menus.
110 *
111 * @todo Why not change split()? That would change the methods post condition.
112 */
113std::vector<std::string> quoted_split(const std::string& val, char c= ',', int flags = REMOVE_EMPTY | STRIP_SPACES, char quote = '\\');
114
115/**
116 * Splits a string based on two separators into a map.
117 *
118 * Major: the separator between elements of the map
119 * Minor: the separator between keys and values in one element
120 *
121 * For example, the string 'a:b,c:d,e:f' would be parsed into:
122 * a => b
123 * c => d
124 * e => f
125 */
126std::map<std::string, std::string> map_split(
127 const std::string& val,
128 char major = ',',
129 char minor = ':',
130 int flags = REMOVE_EMPTY | STRIP_SPACES,
131 const std::string& default_value = "");
132
133/**
134 * Splits a string based either on a separator, except then the text appears within specified parenthesis.
135 *
136 * If the separator is "0" (default), it splits a string into an odd number of parts:
137 * - The part before the first '(',
138 * - the part between the first '('
139 * - and the matching right ')', etc ...
140 * and the remainder of the string.
141 *
142 * Note that one can use the same character for both the left and right parenthesis, which usually makes
143 * the most sense for this function.
144 *
145 * Note that this will find the first matching char in the left string and match against the corresponding
146 * char in the right string. A correctly processed string should return a vector with an odd number of
147 * elements. Empty elements are never removed as they are placeholders, hence REMOVE EMPTY only works for
148 * the separator split.
149 *
150 * INPUT: ("a(b)c{d}e(f{g})h", 0, "({", ")}")
151 * RETURNS: {"a", "b", "c", "d", "e", "f{g}", "h"}
152 */
153std::vector< std::string > parenthetical_split(
154 std::string_view val,
155 const char separator = 0,
156 std::string_view left = "(",
157 std::string_view right = ")",
158 const int flags = REMOVE_EMPTY | STRIP_SPACES);
159
160/**
161 * Similar to parenthetical_split, but also expands embedded square brackets.
162 *
163 * Notes:
164 * - The Separator must be specified and number of entries in each square bracket must match in each section.
165 * - Leading zeros are preserved if specified between square brackets.
166 * - An asterisk as in [a*n] indicates to expand 'a' n times
167 *
168 * This is useful for expanding animation WML code.
169 *
170 * Examples:
171 *
172 * INPUT: ("a[1~3](1,[5,6,7]),b[8,9]", ",")
173 * RETURNS: {"a1(1,5)", "a2(1,6)", "a3(1,7)", "b8", "b9"}
174 *
175 * INPUT: ("abc[07~10]")
176 * RETURNS: {"abc07", "abc08", "abc09", "abc10"}
177 *
178 * INPUT: ("a[1,2]b[3~4]:c[5,6]")
179 * RETURNS: {"a1b3:c5", "a2b4:c6"}
180 *
181 * INPUT: ("abc[3~1].png")
182 * RETURNS: {"abc3.png", "abc2.png", "abc1.png"}
183 *
184 * INPUT: ("abc[3,1].png")
185 * RETURNS: {"abc3.png", "abc1.png"}
186 *
187 * INPUT: ("abc[de,xyz]")
188 * RETURNS: {"abcde", "abcxyz"}
189 *
190 * INPUT: ("abc[1*3]")
191 * RETURNS: {"abc1", "abc1", "abc1"}
192 */
193std::vector<std::string> square_parenthetical_split(
194 const std::string& val,
195 const char separator = ',',
196 const std::string& left = "([",
197 const std::string& right = ")]",
198 const int flags = REMOVE_EMPTY | STRIP_SPACES);
199
200/**
201 * Generates a new string joining container items in a list.
202 *
203 * @param v A container with elements.
204 * @param s List delimiter.
205 */
206template<typename Range>
207std::string join(const Range& v, const std::string& s = ",")
208{
209 std::stringstream str;
210 const auto sentinel = std::end(v);
211
212 for(auto i = std::begin(v); i != sentinel; ++i) {
213 str << *i;
214 if(std::next(i) != sentinel) {
215 str << s;
216 }
217 }
218
219 return str.str();
220}
221
222template <typename T>
223std::string join_map(
224 const T& v,
225 const std::string& major = ",",
226 const std::string& minor = ":")
227{
228 std::stringstream str;
229
230 for(typename T::const_iterator i = v.begin(); i != v.end(); ++i) {
231 str << i->first << minor << i->second;
232 if(std::next(i) != v.end()) {
233 str << major;
234 }
235 }
236
237 return str.str();
238}
239
240/**
241 * Generates a new string containing a bullet list.
242 *
243 * List items are preceded by the indentation blanks, a bullet string and
244 * another blank; all but the last item are followed by a newline.
245 *
246 * @param v A container with elements.
247 * @param indent Number of indentation blanks.
248 * @param bullet The leading bullet string.
249 */
250template<typename T>
251std::string bullet_list(const T& v, std::size_t indent = 4, const std::string& bullet = font::unicode_bullet)
252{
253 std::ostringstream str;
254
255 for(typename T::const_iterator i = v.begin(); i != v.end(); ++i) {
256 if(i != v.begin()) {
257 str << '\n';
258 }
259
260 str << std::string(indent, ' ') << bullet << ' ' << *i;
261 }
262
263 return str.str();
264}
265
266/**
267 * Indent a block of text.
268 *
269 * Only lines with content are changed; empty lines are left intact. However,
270 * if @a string is an empty string itself, the indentation unit with the
271 * specified @a indent_size will be returned instead.
272 *
273 * @param string Text to indent.
274 * @param indent_size Number of indentation units to use.
275 */
276std::string indent(const std::string& string, std::size_t indent_size = 4);
277
278/**
279 * Recognises the following patterns, and returns a {min, max} pair.
280 *
281 * * "1" returns {1, 1}
282 * * "1-3" returns {1, 3}
283 * * "1-infinity" returns {1, maximum int}
284 * * "-1" returns {-1, -1}
285 * * "-3--1" returns {-3, -1}
286 *
287 * Note that:
288 *
289 * * "3-1" returns {3, 3} and does not log an error
290 * * "-1--3" returns {-1, -1} and does not log an error
291 * * Although "-infinity--1", "2-infinity" and "-infinity-infinity" are all supported,
292 * * ranges that can't match a reasonable number, e.g. "-infinity" or "infinity..infinity", may be treated as errors.
293 */
294std::pair<int, int> parse_range(std::string_view str);
295
296/**
297 * Handles a comma-separated list of inputs to parse_range, in a context that does not expect
298 * negative values. Will return an empty list if any of the ranges have a minimum that's below
299 * zero.
300 */
301std::vector<std::pair<int, int>> parse_ranges_unsigned(const std::string& str);
302
303/**
304 * Handles a comma-separated list of inputs to parse_range.
305 */
306std::vector<std::pair<int, int>> parse_ranges_int(const std::string& str);
307
308/**
309 * Recognises similar patterns to parse_range, and returns a {min, max} pair.
310 *
311 * For this function, "infinity" results in std::numeric_limits<double>::infinity.
312 */
313std::pair<double, double> parse_range_real(std::string_view str);
314
315std::vector<std::pair<double, double>> parse_ranges_real(const std::string& str);
316
317int apply_modifier(const int number, const std::string &amount, const int minimum = 0);
318
319/** Add a "+" or replace the "-" par Unicode minus */
320inline std::string print_modifier(const std::string &mod)
321{
322 return mod[0] == '-' ? (font::unicode_minus + std::string(mod.begin() + 1, mod.end())) : ("+" + mod);
323}
324
325/** Format @a str as a WML value */
326inline std::string wml_escape_string(std::string_view str)
327{
328 std::string res;
329
330 for(char c : str) {
331 res.append(c == '"' ? 2 : 1, c);
332 }
333
334 return res;
335}
336
337/** Format @a str as a strongly quoted WML value. Occurances of `<<` are double quoted separately */
338inline std::string wml_escape_strong(const std::string& str)
339{
340 std::string res;
341 std::size_t i = str.find(">>");
342 if(i == std::string::npos) {
343 return str;
344 }
345
346 res.append(str, 0, i);
347 res.append(">>\">>\"<<");
348 std::size_t j;
349 while((j = str.find(">>", i + 2)) != std::string::npos) {
350 res.append(str, i + 2, j - (i + 2));
351 res.append(">>\">>\"<<");
352 i = j;
353 }
354 res.append(str, i + 2);
355
356 return res;
357}
358
359/** Prepends a configurable set of characters with a backslash */
360std::string escape(std::string_view str, const char *special_chars);
361
362/**
363 * Prepend all special characters with a backslash.
364 *
365 * Special characters are:
366 * #@{}+-,\*=
367 */
368inline std::string escape(std::string_view str)
369{
370 return escape(str, "#@{}+-,\\*=");
371}
372
373/** Remove all escape characters (backslash) */
374std::string unescape(std::string_view str);
375
376/** Percent-escape characters in a UTF-8 string intended to be part of a URL. */
377std::string urlencode(std::string_view str);
378
379/** Convert no, false, off, 0, 0.0 to false, empty to def, and others to true */
380bool string_bool(const std::string& str,bool def=false);
381
382/** Converts a bool value to 'true' or 'false' */
383std::string bool_string(const bool value);
384
385/** Convert into a signed value (using the Unicode "−" and +0 convention */
386std::string signed_value(int val);
387
388/** Sign with Unicode "−" if negative */
389std::string half_signed_value(int val);
390
391/** Convert into a percentage (using the Unicode "−" and +0% convention */
392inline std::string signed_percent(int val) {return signed_value(val) + "%";}
393
394/**
395 * Convert into a string with an SI-postfix.
396 *
397 * If the unit is to be translatable,
398 * a t_string should be passed as the third argument.
399 * _("unit_byte^B") is suggested as standard.
400 *
401 * There are no default values because they would not be translatable.
402 */
403std::string si_string(double input, bool base2, const std::string& unit);
404
405/**
406 * Try to complete the last word of 'text' with the 'wordlist'.
407 *
408 * @param[in, out] text The parameter's usage is:
409 * - Input: Text where we try to complete the last word
410 * of.
411 * - Output: Text with completed last word.
412 * @param[in, out] wordlist
413 * The parameter's usage is:
414 * - Inout: A vector of strings to complete against.
415 * - Output: A vector of strings that matched 'text'.
416 *
417 * @retval true iff text is just one word (no spaces)
418 */
419bool word_completion(std::string& text, std::vector<std::string>& wordlist);
420
421/** Check if a message contains a word. */
422bool word_match(const std::string& message, const std::string& word);
423
424/**
425 * @brief Performs pattern matching with wildcards.
426 *
427 * @param str Any byte-string.
428 * @param pat A string of characters with the following interpretation:
429 * - @c '*' represents zero or more characters.
430 * - @c '+' represents one or more characters.
431 * - @c '?' represents exactly one character.
432 * - All other characters are interpreted literally.
433 *
434 * @returns @c true if @p str matches @p pat
435 */
436[[nodiscard]] bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept;
437
438/**
439 * Converts '*' to '%' and optionally escapes '_'.
440 *
441 * @param str The original string.
442 * @param underscores Whether to escape underscore characters as well.
443 */
444void to_sql_wildcards(std::string& str, bool underscores = true);
445
446/**
447 * Check if the username contains only valid characters.
448 *
449 * (all alpha-numeric characters plus underscore and hyphen)
450 */
451bool isvalid_username(const std::string& login);
452
453/**
454 * Check if the username pattern contains only valid characters.
455 *
456 * (all alpha-numeric characters plus underscore, hyphen,
457 * question mark and asterisk)
458 */
459bool isvalid_wildcard(const std::string& login);
460
461/**
462 * Truncates a string to a given utf-8 character count and then appends an ellipsis.
463 */
464void ellipsis_truncate(std::string& str, const std::size_t size);
465
466} // end namespace utils
This class represents a single unit of a specific type.
Definition unit.hpp:39
static int indent
Definition log.cpp:88
const std::string unicode_bullet
Definition constants.cpp:47
const std::string unicode_minus
Definition constants.cpp:42
T end(const std::pair< T, T > &p)
T begin(const std::pair< T, T > &p)
@ STRIP_SPACES
REMOVE_EMPTY: remove empty elements.
std::vector< std::string_view > split_view(std::string_view s, const char sep, const int flags)
std::string si_string(double input, bool base2, const std::string &unit)
Convert into a string with an SI-postfix.
void trim(std::string_view &s)
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.
bool isvalid_wildcard(const std::string &username)
Check if the username pattern contains only valid characters.
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
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 join_map(const T &v, const std::string &major=",", const std::string &minor=":")
std::vector< std::pair< int, int > > parse_ranges_int(const std::string &str)
Handles a comma-separated list of inputs to parse_range.
std::string bullet_list(const T &v, std::size_t indent=4, const std::string &bullet=font::unicode_bullet)
Generates a new string containing a bullet list.
void split_foreach_impl(std::string_view s, char sep, const F &f)
std::vector< std::string > parenthetical_split(std::string_view val, const char separator, std::string_view left, std::string_view right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
const std::vector< std::string > res_order
bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept
Performs pattern matching with wildcards.
std::string half_signed_value(int val)
Sign with Unicode "−" if negative.
std::string bool_string(const bool value)
Converts a bool value to 'true' or 'false'.
std::map< std::string, t_string, res_compare > string_map_res
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
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.
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
std::string urlencode(std::string_view str)
Percent-escape characters in a UTF-8 string intended to be part of a URL.
void to_sql_wildcards(std::string &str, bool underscores)
Converts '*' to '' and optionally escapes '_'.
void split_foreach(std::string_view s, char sep, const int flags, const F &f)
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 isvalid_username(const std::string &username)
Check if the username contains only valid characters.
std::string wml_escape_strong(const std::string &str)
Format str as a strongly quoted WML value.
bool portable_isspace(const char c)
int apply_modifier(const int number, const std::string &amount, const int minimum)
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< int, int > parse_range(std::string_view str)
Recognises the following patterns, and returns a {min, max} pair.
bool isnewline(const char c)
bool notspace(const char c)
std::string unescape(std::string_view str)
Remove all escape characters (backslash)
std::vector< std::pair< double, double > > parse_ranges_real(const std::string &str)
bool word_match(const std::string &message, const std::string &word)
Check if a message contains a word.
std::string escape(std::string_view str, const char *special_chars)
Prepends a configurable set of characters with a backslash.
std::string signed_value(int val)
Convert into a signed value (using the Unicode "−" and +0 convention.
std::map< std::string, t_string > string_map
std::string signed_percent(int val)
Convert into a percentage (using the Unicode "−" and +0% convention.
std::pair< double, double > parse_range_real(std::string_view str)
Recognises similar patterns to parse_range, and returns a {min, max} pair.
std::vector< std::string > split(const config_attribute_value &val)
bool word_completion(std::string &text, std::vector< std::string > &wordlist)
Try to complete the last word of 'text' with the 'wordlist'.
std::string wml_escape_string(std::string_view str)
Format str as a WML value
std::string print_modifier(const std::string &mod)
Add a "+" or replace the "-" par Unicode minus.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
bool operator()(const std::string &a, const std::string &b) const
Returns whether a < b, considering res_order.
static map_location::direction s
#define i
#define f
#define c
#define b