1 #ifndef LIBFILEZILLA_STRING_HEADER
2 #define LIBFILEZILLA_STRING_HEADER
3
4 #include "libfilezilla.hpp"
5
6 #include <algorithm>
7 #include <string>
8 #include <string_view>
9 #include <vector>
10
11 /** \file
12 * \brief String types and assorted functions.
13 *
14 * Defines the \ref fz::native_string type and offers various functions to convert between
15 * different string types.
16 */
17
18 namespace fz {
19
20 /** \typedef native_string
21 *
22 * \brief A string in the system's native character type and encoding.\n Note: This typedef changes depending on platform!
23 *
24 * On Windows, the system's native encoding is UTF-16, so native_string is typedef'ed to std::wstring.
25 *
26 * On all other platform, native_string is a typedef for std::string.
27 *
28 * Always using native_string has the benefit that no conversion needs to be performed which is especially useful
29 * if dealing with filenames.
30 */
31
32 #ifdef FZ_WINDOWS
33 typedef std::wstring native_string;
34 typedef std::wstring_view native_string_view;
35 #endif
36 #if defined(FZ_UNIX) || defined(FZ_MAC)
37 typedef std::string native_string;
38 typedef std::string_view native_string_view;
39 #endif
40
41 /** \brief Converts std::string to native_string.
42 *
43 * \return the converted string on success. On failure an empty string is returned.
44 */
45 native_string FZ_PUBLIC_SYMBOL to_native(std::string_view const& in);
46
47 /** \brief Convert std::wstring to native_string.
48 *
49 * \return the converted string on success. On failure an empty string is returned.
50 */
51 native_string FZ_PUBLIC_SYMBOL to_native(std::wstring_view const& in);
52
53 /// Avoid converting native_string to native_string_view and back to native_string
54 template<typename T, typename std::enable_if_t<std::is_same_v<native_string, typename std::decay_t<T>>, int> = 0>
to_native(T const & in)55 inline native_string to_native(T const& in) {
56 return in;
57 }
58
59 /** \brief Locale-sensitive stricmp
60 *
61 * Like std::string::compare but case-insensitive, respecting locale.
62 *
63 * \note does not handle embedded null
64 */
65 int FZ_PUBLIC_SYMBOL stricmp(std::string_view const& a, std::string_view const& b);
66 int FZ_PUBLIC_SYMBOL stricmp(std::wstring_view const& a, std::wstring_view const& b);
67
68 /** \brief Converts ASCII uppercase characters to lowercase as if C-locale is used.
69
70 Under some locales there is a different case-relationship
71 between the letters a-z and A-Z as one expects from ASCII under the C locale.
72 In Turkish for example there are different variations of the letter i,
73 namely dotted and dotless. What we see as 'i' is the lowercase dotted i and
74 'I' is the uppercase dotless i. Since std::tolower is locale-aware, I would
75 become the dotless lowercase i.
76
77 This is not always what we want. FTP commands for example are case-insensitive
78 ASCII strings, LIST and list are the same.
79
80 tolower_ascii instead converts all types of 'i's to the ASCII i as well.
81
82 \return A-Z becomes a-z.\n In addition dotless lowercase i and dotted uppercase i also become the standard i.
83
84 */
85 template<typename Char>
tolower_ascii(Char c)86 Char tolower_ascii(Char c) {
87 if (c >= 'A' && c <= 'Z') {
88 return c + ('a' - 'A');
89 }
90 return c;
91 }
92
93 template<>
94 std::wstring::value_type FZ_PUBLIC_SYMBOL tolower_ascii(std::wstring::value_type c);
95
96 /// \brief Converts ASCII lowercase characters to uppercase as if C-locale is used.
97 template<typename Char>
toupper_ascii(Char c)98 Char toupper_ascii(Char c) {
99 if (c >= 'a' && c <= 'z') {
100 return c + ('A' - 'a');
101 }
102 return c;
103 }
104
105 template<>
106 std::wstring::value_type FZ_PUBLIC_SYMBOL toupper_ascii(std::wstring::value_type c);
107
108 /** \brief tr_tolower_ascii does for strings what tolower_ascii does for individual characters
109 */
110 // Note: For UTF-8 strings it works on individual octets!
111 std::string FZ_PUBLIC_SYMBOL str_tolower_ascii(std::string_view const& s);
112 std::wstring FZ_PUBLIC_SYMBOL str_tolower_ascii(std::wstring_view const& s);
113
114 std::string FZ_PUBLIC_SYMBOL str_toupper_ascii(std::string_view const& s);
115 std::wstring FZ_PUBLIC_SYMBOL str_toupper_ascii(std::wstring_view const& s);
116
117 /** \brief Comparator to be used for std::map for case-insensitive keys
118 *
119 * Comparison is done locale-agnostic.
120 * Useful for key-value pairs in protocols, e.g. HTTP headers.
121 */
122 struct FZ_PUBLIC_SYMBOL less_insensitive_ascii final
123 {
124 template<typename T>
operator ()fz::less_insensitive_ascii125 bool operator()(T const& lhs, T const& rhs) const {
126 return std::lexicographical_compare(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(),
127 [](typename T::value_type const& a, typename T::value_type const& b) {
128 return tolower_ascii(a) < tolower_ascii(b);
129 }
130 );
131 }
132 };
133
134 /** \brief Locale-insensitive stricmp
135 *
136 * Equivalent to str_tolower_ascii(a).compare(str_tolower_ascii(b));
137 */
equal_insensitive_ascii(std::string_view a,std::string_view b)138 inline bool equal_insensitive_ascii(std::string_view a, std::string_view b)
139 {
140 return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend(),
141 [](auto const& a, auto const& b) {
142 return tolower_ascii(a) == tolower_ascii(b);
143 }
144 );
145 }
equal_insensitive_ascii(std::wstring_view a,std::wstring_view b)146 inline bool equal_insensitive_ascii(std::wstring_view a, std::wstring_view b)
147 {
148 return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend(),
149 [](auto const& a, auto const& b) {
150 return tolower_ascii(a) == tolower_ascii(b);
151 }
152 );
153 }
154
155 /** \brief Converts from std::string in system encoding into std::wstring
156 *
157 * \return the converted string on success. On failure an empty string is returned.
158 */
159 std::wstring FZ_PUBLIC_SYMBOL to_wstring(std::string_view const& in);
to_wstring(std::wstring_view const & in)160 inline std::wstring FZ_PUBLIC_SYMBOL to_wstring(std::wstring_view const& in) { return std::wstring(in); }
161
162 /** \brief Returns identity, that way to_wstring can be called with native_string.
163 *
164 * This template prevents converting std::wstring to std::wstring_view and back to std::wstring
165 */
166 template<typename T, typename std::enable_if_t<std::is_same_v<std::wstring, typename std::decay_t<T>>, int> = 0>
to_wstring(T const & in)167 inline std::wstring to_wstring(T const& in) {
168 return in;
169 }
170
171 /// Converts from arithmetic type to std::wstring
172 template<typename Arg>
to_wstring(Arg && arg)173 inline typename std::enable_if<std::is_arithmetic_v<std::decay_t<Arg>>, std::wstring>::type to_wstring(Arg && arg)
174 {
175 return std::to_wstring(std::forward<Arg>(arg));
176 }
177
178
179 /** \brief Converts from std::string in UTF-8 into std::wstring
180 *
181 * \return the converted string on success. On failure an empty string is returned.
182 */
183 std::wstring FZ_PUBLIC_SYMBOL to_wstring_from_utf8(std::string_view const& in);
184 std::wstring FZ_PUBLIC_SYMBOL to_wstring_from_utf8(char const* s, size_t len);
185
186 class buffer;
187 std::wstring FZ_PUBLIC_SYMBOL to_wstring_from_utf8(fz::buffer const& in);
188
189 /** \brief Converts from std::wstring into std::string in system encoding
190 *
191 * \return the converted string on success. On failure an empty string is returned.
192 */
193 std::string FZ_PUBLIC_SYMBOL to_string(std::wstring_view const& in);
to_string(std::string_view const & in)194 inline std::string FZ_PUBLIC_SYMBOL to_string(std::string_view const& in) { return std::string(in); }
195
196 /** \brief Returns identity, that way to_wstring can be called with native_string.
197 *
198 * This template prevents converting std::wstring to std::wstring_view and back to std::wstring
199 */
200 template<typename T, typename std::enable_if_t<std::is_same_v<std::string, typename std::decay_t<T>>, int> = 0>
to_string(T const & in)201 inline std::string to_string(T const& in) {
202 return in;
203 }
204
205
206 /// Converts from arithmetic type to std::string
207 template<typename Arg>
to_string(Arg && arg)208 inline typename std::enable_if<std::is_arithmetic_v<std::decay_t<Arg>>, std::string>::type to_string(Arg && arg)
209 {
210 return std::to_string(std::forward<Arg>(arg));
211 }
212
213
214 /// Returns length of 0-terminated character sequence. Works with both narrow and wide-characters.
215 template<typename Char>
strlen(Char const * str)216 size_t strlen(Char const* str) {
217 return std::char_traits<Char>::length(str);
218 }
219
220
221 /** \brief Converts from std::string in native encoding into std::string in UTF-8
222 *
223 * \return the converted string on success. On failure an empty string is returned.
224 *
225 * \note Does not handle embedded nulls
226 */
227 std::string FZ_PUBLIC_SYMBOL to_utf8(std::string_view const& in);
228
229 /** \brief Converts from std::wstring in native encoding into std::string in UTF-8
230 *
231 * \return the converted string on success. On failure an empty string is returned.
232 *
233 * \note Does not handle embedded nulls
234 */
235 std::string FZ_PUBLIC_SYMBOL to_utf8(std::wstring_view const& in);
236
237 /// Calls either fz::to_string or fz::to_wstring depending on the passed template argument
238 template<typename String, typename Arg>
toString(Arg && arg)239 inline auto toString(Arg&& arg) -> typename std::enable_if<std::is_same_v<String, std::string>, decltype(to_string(std::forward<Arg>(arg)))>::type
240 {
241 return to_string(std::forward<Arg>(arg));
242 }
243
244 template<typename String, typename Arg>
toString(Arg && arg)245 inline auto toString(Arg&& arg) -> typename std::enable_if<std::is_same_v<String, std::wstring>, decltype(to_wstring(std::forward<Arg>(arg)))>::type
246 {
247 return to_wstring(std::forward<Arg>(arg));
248 }
249
250 #if !defined(fzT) || defined(DOXYGEN)
251 #ifdef FZ_WINDOWS
252 /** \brief Macro for a string literal in system-native character type.\n Note: Macro definition changes depending on platform!
253 *
254 * Example: \c fzT("this string is wide on Windows and narrow elsewhere")
255 */
256 #define fzT(x) L ## x
257 #else
258 /** \brief Macro for a string literal in system-native character type.\n Note: Macro definition changes depending on platform!
259 *
260 * Example: \c fzT("this string is wide on Windows and narrow elsewhere")
261 */
262 #define fzT(x) x
263 #endif
264 #endif
265
266 /// Returns the function argument of the type matching the template argument. \sa fzS
267 template<typename Char>
268 Char const* choose_string(char const* c, wchar_t const* w);
269
choose_string(char const * c,wchar_t const *)270 template<> inline char const* choose_string(char const* c, wchar_t const*) { return c; }
choose_string(char const *,wchar_t const * w)271 template<> inline wchar_t const* choose_string(char const*, wchar_t const* w) { return w; }
272
273 #if !defined(fzS) || defined(DOXYGEN)
274 /** \brief Macro to get const pointer to a string of the corresponding type
275 *
276 * Useful when using string literals in templates where the type of string
277 * is a template argument:
278 * \code
279 * template<typename String>
280 * String append_foo(String const& s) {
281 * s += fzS(String::value_type, "foo");
282 * }
283 * \endcode
284 */
285 #define fzS(Char, s) fz::choose_string<Char>(s, L ## s)
286 #endif
287
288 /** \brief Returns \c in with all occurrences of \c find in the input string replaced with \c replacement
289 *
290 * \arg find If empty, no replacement takes place.
291 */
292 std::string FZ_PUBLIC_SYMBOL replaced_substrings(std::string_view const& in, std::string_view const& find, std::string_view const& replacement);
293 std::wstring FZ_PUBLIC_SYMBOL replaced_substrings(std::wstring_view const& in, std::wstring_view const& find, std::wstring_view const& replacement);
294
295 /// Returns \c in with all occurrences of \c find in the input string replaced with \c replacement
296 std::string FZ_PUBLIC_SYMBOL replaced_substrings(std::string_view const& in, char find, char replacement);
297 std::wstring FZ_PUBLIC_SYMBOL replaced_substrings(std::wstring_view const& in, wchar_t find, wchar_t replacement);
298
299 /** \brief Modifies \c in, replacing all occurrences of \c find with \c replacement
300 *
301 * \arg find If empty, no replacement takes place.
302 */
303 bool FZ_PUBLIC_SYMBOL replace_substrings(std::string& in, std::string_view const& find, std::string_view const& replacement);
304 bool FZ_PUBLIC_SYMBOL replace_substrings(std::wstring& in, std::wstring_view const& find, std::wstring_view const& replacement);
305
306 /// Modifies \c in, replacing all occurrences of \c find with \c replacement
307 bool FZ_PUBLIC_SYMBOL replace_substrings(std::string& in, char find, char replacement);
308 bool FZ_PUBLIC_SYMBOL replace_substrings(std::wstring& in, wchar_t find, wchar_t replacement);
309
310 /**
311 * \brief Tokenizes string.
312 *
313 * \param delims the delimiters to look for
314 * \param ignore_empty If true, empty tokens are omitted in the output
315 */
316 std::vector<std::string> FZ_PUBLIC_SYMBOL strtok(std::string_view const& tokens, std::string_view const& delims, bool const ignore_empty = true);
317 std::vector<std::wstring> FZ_PUBLIC_SYMBOL strtok(std::wstring_view const& tokens, std::wstring_view const& delims, bool const ignore_empty = true);
strtok(std::string_view const & tokens,char const delim,bool const ignore_empty=true)318 inline auto FZ_PUBLIC_SYMBOL strtok(std::string_view const& tokens, char const delim, bool const ignore_empty = true) {
319 return strtok(tokens, std::string_view(&delim, 1), ignore_empty);
320 }
strtok(std::wstring_view const & tokens,wchar_t const delim,bool const ignore_empty=true)321 inline auto FZ_PUBLIC_SYMBOL strtok(std::wstring_view const& tokens, wchar_t const delim, bool const ignore_empty = true) {
322 return strtok(tokens, std::wstring_view(&delim, 1), ignore_empty);
323 }
324
325 /**
326 * \brief Tokenizes string.
327 *
328 * \warning This function returns string_views, mind the lifetime of the string passed in tokens.
329 *
330 * \param delims the delimiters to look for
331 * \param ignore_empty If true, empty tokens are omitted in the output
332 */
333 std::vector<std::string_view> FZ_PUBLIC_SYMBOL strtok_view(std::string_view const& tokens, std::string_view const& delims, bool const ignore_empty = true);
334 std::vector<std::wstring_view> FZ_PUBLIC_SYMBOL strtok_view(std::wstring_view const& tokens, std::wstring_view const& delims, bool const ignore_empty = true);
strtok_view(std::string_view const & tokens,char const delim,bool const ignore_empty=true)335 inline auto FZ_PUBLIC_SYMBOL strtok_view(std::string_view const& tokens, char const delim, bool const ignore_empty = true) {
336 return strtok_view(tokens, std::string_view(&delim, 1), ignore_empty);
337 }
strtok_view(std::wstring_view const & tokens,wchar_t const delim,bool const ignore_empty=true)338 inline auto FZ_PUBLIC_SYMBOL strtok_view(std::wstring_view const& tokens, wchar_t const delim, bool const ignore_empty = true) {
339 return strtok_view(tokens, std::wstring_view(&delim, 1), ignore_empty);
340 }
341
342 /// \private
343 template<typename T, typename String>
344 T to_integral_impl(String const& s, T const errorval = T())
345 {
346 if constexpr (std::is_same_v<T, bool>) {
347 return static_cast<T>(to_integral_impl<unsigned int>(s, static_cast<unsigned int>(errorval))) != 0;
348 }
349 else if constexpr (std::is_enum_v<T>) {
350 return static_cast<T>(to_integral_impl<std::underlying_type_t<T>>(s, static_cast<std::underlying_type_t<T>>(errorval)));
351 }
352 else {
353 T ret{};
354 auto it = s.cbegin();
355 if (it != s.cend() && (*it == '-' || *it == '+')) {
356 ++it;
357 }
358
359 if (it == s.cend()) {
360 return errorval;
361 }
362
363 for (; it != s.cend(); ++it) {
364 auto const& c = *it;
365 if (c < '0' || c > '9') {
366 return errorval;
367 }
368 ret *= 10;
369 ret += c - '0';
370 }
371
372 if (!s.empty() && s.front() == '-') {
373 ret *= static_cast<T>(-1);
374 }
375 return ret;
376 }
377 }
378
379 /// Converts string to integral type T. If string is not convertible, errorval is returned.
380 template<typename T>
to_integral(std::string_view const & s,T const errorval=T ())381 T to_integral(std::string_view const& s, T const errorval = T()) {
382 return to_integral_impl<T>(s, errorval);
383 }
384
385 template<typename T>
to_integral(std::wstring_view const & s,T const errorval=T ())386 T to_integral(std::wstring_view const& s, T const errorval = T()) {
387 return to_integral_impl<T>(s, errorval);
388 }
389
390 template<typename T, typename StringType>
391 T to_integral(std::basic_string_view<StringType> const& s, T const errorval = T()) {
392 return to_integral_impl<T>(s, errorval);
393 }
394
395
396 /// \brief Returns true iff the string only has characters in the 7-bit ASCII range
397 template<typename String>
str_is_ascii(String const & s)398 bool str_is_ascii(String const& s) {
399 for (auto const& c : s) {
400 if (static_cast<std::make_unsigned_t<typename String::value_type>>(c) > 127) {
401 return false;
402 }
403 }
404
405 return true;
406 }
407
408 /// \private
409 template<typename String, typename Chars>
trim_impl(String & s,Chars const & chars,bool fromLeft,bool fromRight)410 void trim_impl(String & s, Chars const& chars, bool fromLeft, bool fromRight) {
411 size_t const first = fromLeft ? s.find_first_not_of(chars) : 0;
412 if (first == String::npos) {
413 s = String();
414 return;
415 }
416
417 size_t const last = fromRight ? s.find_last_not_of(chars) : s.size();
418 if (last == String::npos) {
419 s = String();
420 return;
421 }
422
423 // Invariant: If first exists, then last >= first
424 s = s.substr(first, last - first + 1);
425 }
426
427 /// \brief Return passed string with all leading and trailing whitespace removed
trimmed(std::string_view s,std::string_view const & chars=" \\r\\n\\t",bool fromLeft=true,bool fromRight=true)428 inline std::string FZ_PUBLIC_SYMBOL trimmed(std::string_view s, std::string_view const& chars = " \r\n\t", bool fromLeft = true, bool fromRight = true)
429 {
430 trim_impl(s, chars, fromLeft, fromRight);
431 return std::string(s);
432 }
433
trimmed(std::wstring_view s,std::wstring_view const & chars=L" \\r\\n\\t",bool fromLeft=true,bool fromRight=true)434 inline std::wstring FZ_PUBLIC_SYMBOL trimmed(std::wstring_view s, std::wstring_view const& chars = L" \r\n\t", bool fromLeft = true, bool fromRight = true)
435 {
436 trim_impl(s, chars, fromLeft, fromRight);
437 return std::wstring(s);
438 }
439
ltrimmed(std::string_view s,std::string_view const & chars=" \\r\\n\\t")440 inline std::string FZ_PUBLIC_SYMBOL ltrimmed(std::string_view s, std::string_view const& chars = " \r\n\t")
441 {
442 trim_impl(s, chars, true, false);
443 return std::string(s);
444 }
445
ltrimmed(std::wstring_view s,std::wstring_view const & chars=L" \\r\\n\\t")446 inline std::wstring FZ_PUBLIC_SYMBOL ltrimmed(std::wstring_view s, std::wstring_view const& chars = L" \r\n\t")
447 {
448 trim_impl(s, chars, true, false);
449 return std::wstring(s);
450 }
451
rtrimmed(std::string_view s,std::string_view const & chars=" \\r\\n\\t")452 inline std::string FZ_PUBLIC_SYMBOL rtrimmed(std::string_view s, std::string_view const& chars = " \r\n\t")
453 {
454 trim_impl(s, chars, false, true);
455 return std::string(s);
456 }
457
rtrimmed(std::wstring_view s,std::wstring_view const & chars=L" \\r\\n\\t")458 inline std::wstring FZ_PUBLIC_SYMBOL rtrimmed(std::wstring_view s, std::wstring_view const& chars = L" \r\n\t")
459 {
460 trim_impl(s, chars, false, true);
461 return std::wstring(s);
462 }
463
464
465 /// \brief Remove all leading and trailing whitespace from string
466 template<typename String, typename std::enable_if_t<std::is_same_v<typename String::value_type, char>, int> = 0>
trim(String & s,std::string_view const & chars=" \\r\\n\\t",bool fromLeft=true,bool fromRight=true)467 inline void trim(String & s, std::string_view const& chars = " \r\n\t", bool fromLeft = true, bool fromRight = true)
468 {
469 trim_impl(s, chars, fromLeft, fromRight);
470 }
471
472 template<typename String, typename std::enable_if_t<std::is_same_v<typename String::value_type, wchar_t>, int> = 0>
trim(String & s,std::wstring_view const & chars=L" \\r\\n\\t",bool fromLeft=true,bool fromRight=true)473 inline void trim(String & s, std::wstring_view const& chars = L" \r\n\t", bool fromLeft = true, bool fromRight = true)
474 {
475 trim_impl(s, chars, fromLeft, fromRight);
476 }
477
478 template<typename String, typename std::enable_if_t<std::is_same_v<typename String::value_type, char>, int> = 0>
ltrim(String & s,std::string_view const & chars=" \\r\\n\\t")479 inline void ltrim(String& s, std::string_view const& chars = " \r\n\t")
480 {
481 trim_impl(s, chars, true, false);
482 }
483
484 template<typename String, typename std::enable_if_t<std::is_same_v<typename String::value_type, wchar_t>, int> = 0>
ltrim(String & s,std::wstring_view const & chars=L" \\r\\n\\t")485 inline void ltrim(String& s, std::wstring_view const& chars = L" \r\n\t")
486 {
487 trim_impl(s, chars, true, false);
488 }
489
490 template<typename String, typename std::enable_if_t<std::is_same_v<typename String::value_type, char>, int> = 0>
rtrim(String & s,std::string_view const & chars=" \\r\\n\\t")491 inline void rtrim(String& s, std::string_view const& chars = " \r\n\t")
492 {
493 trim_impl(s, chars, false, true);
494 }
495
496 template<typename String, typename std::enable_if_t<std::is_same_v<typename String::value_type, wchar_t>, int> = 0>
rtrim(String & s,std::wstring_view const & chars=L" \\r\\n\\t")497 inline void rtrim(String & s, std::wstring_view const& chars = L" \r\n\t")
498 {
499 trim_impl(s, chars, false, true);
500 }
501
502 /** \brief Tests whether the first string starts with the second string
503 *
504 * \tparam insensitive_ascii If true, comparison is case-insensitive
505 */
506 template<bool insensitive_ascii = false, typename String>
starts_with(String const & s,String const & beginning)507 bool starts_with(String const& s, String const& beginning)
508 {
509 if (beginning.size() > s.size()) {
510 return false;
511 }
512 if constexpr (insensitive_ascii) {
513 return std::equal(beginning.begin(), beginning.end(), s.begin(), [](typename String::value_type const& a, typename String::value_type const& b) {
514 return tolower_ascii(a) == tolower_ascii(b);
515 });
516 }
517 else {
518 return std::equal(beginning.begin(), beginning.end(), s.begin());
519 }
520 }
521
522 /** \brief Tests whether the first string ends with the second string
523 *
524 * \tparam insensitive_ascii If true, comparison is case-insensitive
525 */
526 template<bool insensitive_ascii = false, typename String>
ends_with(String const & s,String const & ending)527 bool ends_with(String const& s, String const& ending)
528 {
529 if (ending.size() > s.size()) {
530 return false;
531 }
532
533 if constexpr (insensitive_ascii) {
534 return std::equal(ending.rbegin(), ending.rend(), s.rbegin(), [](typename String::value_type const& a, typename String::value_type const& b) {
535 return tolower_ascii(a) == tolower_ascii(b);
536 });
537 }
538 else {
539 return std::equal(ending.rbegin(), ending.rend(), s.rbegin());
540 }
541 }
542
543 /**
544 * Normalizes various hyphens, dashes and minuses to just hyphen-minus.
545 *
546 * The narrow version assumes UTF-8 as encoding.
547 */
548 std::string FZ_PUBLIC_SYMBOL normalize_hyphens(std::string_view const& in);
549 std::wstring FZ_PUBLIC_SYMBOL normalize_hyphens(std::wstring_view const& in);
550
551 }
552
553 #endif
554