C++ Utilities 5.24.7
Useful C++ classes and routines such as argument parser, IO and conversion utilities
Loading...
Searching...
No Matches
stringconversion.h
Go to the documentation of this file.
1#ifndef CONVERSION_UTILITIES_STRINGCONVERSION_H
2#define CONVERSION_UTILITIES_STRINGCONVERSION_H
3
6
7#include "../misc/traits.h"
8
9#include <cstdlib>
10#include <cstring>
11#include <initializer_list>
12#include <iomanip>
13#include <list>
14#include <memory>
15#include <sstream>
16#include <string>
17#include <string_view>
18#include <system_error>
19#include <vector>
20
21#if __cplusplus >= 201709 && !defined(REFLECTIVE_RAPIDJSON_GENERATOR)
22#ifndef CPP_UTILITIES_USE_RANGES
23#define CPP_UTILITIES_USE_RANGES
24#endif
25#include <ranges>
26#endif
27
28namespace CppUtilities {
29
38 void operator()(char *stringData)
39 {
40 std::free(stringData);
41 }
42};
43
47using StringData = std::pair<std::unique_ptr<char[], StringDataDeleter>, std::size_t>;
48//using StringData = std::pair<std::unique_ptr<char>, std::size_t>; // might work too
49
51 const char *fromCharset, const char *toCharset, const char *inputBuffer, std::size_t inputBufferSize, float outputBufferSizeFactor = 1.0f);
58
59#ifdef PLATFORM_WINDOWS
60using WideStringData = std::pair<std::unique_ptr<wchar_t[]>, int>;
61CPP_UTILITIES_EXPORT std::wstring convertMultiByteToWide(std::error_code &ec, std::string_view inputBuffer);
63CPP_UTILITIES_EXPORT WideStringData convertMultiByteToWide(std::error_code &ec, const std::string &inputBuffer);
66#endif
67
68CPP_UTILITIES_EXPORT void truncateString(std::string &str, char terminationChar = '\0');
69
71namespace Detail {
72#ifdef CPP_UTILITIES_USE_RANGES
73template <class Container>
74using ContainerValueType = typename std::conditional_t<std::ranges::range<Container>,
75 std::iterator_traits<std::remove_cvref_t<std::ranges::iterator_t<Container>>>, Container>::value_type;
76#else
77template <class Container> using ContainerValueType = typename Container::value_type;
78#endif
79template <class Container> using DefaultReturnTypeForContainer = ContainerValueType<Container>;
80template <class Container> using StringParamForContainer = std::basic_string_view<typename ContainerValueType<Container>::value_type>;
81} // namespace Detail
83
98template <class Container = std::initializer_list<std::string>, class ReturnType = Detail::DefaultReturnTypeForContainer<Container>>
99ReturnType joinStrings(const Container &strings, Detail::StringParamForContainer<Container> delimiter = Detail::StringParamForContainer<Container>(),
100 bool omitEmpty = false, Detail::StringParamForContainer<Container> leftClosure = Detail::StringParamForContainer<Container>(),
101 Detail::StringParamForContainer<Container> rightClosure = Detail::StringParamForContainer<Container>())
102{
104 if (!strings.size()) {
105 return res;
106 }
107 std::size_t entries = 0, size = 0;
108 for (const auto &str : strings) {
109 if (omitEmpty && str.empty()) {
110 continue;
111 }
112 size += str.size();
113 ++entries;
114 }
115 if (!entries) {
116 return res;
117 }
118 size += (entries * leftClosure.size()) + (entries * rightClosure.size()) + ((entries - 1) * delimiter.size());
119 res.reserve(size);
120 for (const auto &str : strings) {
121 if (omitEmpty && str.empty()) {
122 continue;
123 }
124 if (!res.empty()) {
125 res.append(delimiter);
126 }
127 res.append(leftClosure);
128 res.append(str);
129 res.append(rightClosure);
130 }
131 return res;
132}
133
137template <class Container = std::initializer_list<std::string>> inline auto toMultiline(const Container &arrayOfLines)
138{
139 return joinStrings(arrayOfLines, "\n", false);
140}
141
145enum class EmptyPartsTreat {
146 Keep,
147 Omit,
148 Merge
149};
150
160template <class Container = std::list<std::string>>
161Container splitString(Detail::StringParamForContainer<Container> string, Detail::StringParamForContainer<Container> delimiter,
163{
164 --maxParts;
166 typename Container::value_type *last = nullptr;
167 bool merge = false;
168 typename Container::value_type::size_type i = 0, end = string.size();
169 for (typename Container::value_type::size_type delimPos; i < end; i = delimPos + delimiter.size()) {
170 delimPos = string.find(delimiter, i);
171 if (!merge && maxParts >= 0 && res.size() == static_cast<typename Container::value_type::size_type>(maxParts)) {
173 if (last) {
174 merge = true;
175 continue;
176 }
177 }
178 delimPos = Container::value_type::npos;
179 }
180 if (delimPos == Container::value_type::npos) {
181 delimPos = string.size();
182 }
184 if (merge) {
185 last->append(delimiter);
186 last->append(string, i, delimPos - i);
187 merge = false;
188 } else {
189 last = &res.emplace_back(string, i, delimPos - i);
190 }
192 if (last) {
193 merge = true;
194 }
195 }
196 }
197 if (i == end && emptyPartsRole == EmptyPartsTreat::Keep) {
198 res.emplace_back();
199 }
200 return res;
201}
202
212template <class Container = std::list<std::string>>
214 Detail::StringParamForContainer<Container> string, Detail::StringParamForContainer<Container> delimiter, int maxParts = -1)
215{
216 --maxParts;
218 typename Container::value_type::size_type i = 0, end = string.size();
219 for (typename Container::value_type::size_type delimPos; i < end; i = delimPos + delimiter.size()) {
220 delimPos = string.find(delimiter, i);
221 if (maxParts >= 0 && res.size() == static_cast<typename Container::value_type::size_type>(maxParts)) {
222 delimPos = Container::value_type::npos;
223 }
224 if (delimPos == Container::value_type::npos) {
225 delimPos = string.size();
226 }
227#if __cplusplus >= 201709
228 if constexpr (requires { res.emplace_back(string); }) {
229#endif
230 res.emplace_back(string.data() + i, delimPos - i);
231#if __cplusplus >= 201709
232 } else {
233 res.emplace(string.data() + i, delimPos - i);
234 }
235#endif
236 }
237 if (i == end) {
238#if __cplusplus >= 201709
239 if constexpr (requires { res.emplace_back(); }) {
240#endif
241 res.emplace_back();
242#if __cplusplus >= 201709
243 } else {
244 res.emplace();
245 }
246#endif
247 }
248 return res;
249}
250
254template <class Container = std::vector<std::string>> inline auto toArrayOfLines(const std::string &multilineString)
255{
257}
258
262template <typename StringType> bool startsWith(const StringType &str, const StringType &phrase)
263{
264 if (str.size() < phrase.size()) {
265 return false;
266 }
267 for (auto stri = str.cbegin(), strend = str.cend(), phrasei = phrase.cbegin(), phraseend = phrase.cend();; ++stri, ++phrasei) {
268 if (phrasei == phraseend) {
269 return true;
270 } else if (stri == strend) {
271 return false;
272 } else if (*stri != *phrasei) {
273 return false;
274 }
275 }
276 return false;
277}
278
282template <typename StringType> bool startsWith(const StringType &str, const typename StringType::value_type *phrase)
283{
284 for (auto stri = str.cbegin(), strend = str.cend();; ++stri, ++phrase) {
285 if (!*phrase) {
286 return true;
287 } else if (stri == strend) {
288 return false;
289 } else if (*stri != *phrase) {
290 return false;
291 }
292 }
293 return false;
294}
295
299template <typename StringType> bool endsWith(const StringType &str, const StringType &phrase)
300{
301 if (str.size() < phrase.size()) {
302 return false;
303 }
304 for (auto stri = str.cend() - static_cast<typename StringType::difference_type>(phrase.size()), strend = str.cend(), phrasei = phrase.cbegin();
305 stri != strend; ++stri, ++phrasei) {
306 if (*stri != *phrasei) {
307 return false;
308 }
309 }
310 return true;
311}
312
316template <typename StringType> bool endsWith(const StringType &str, const typename StringType::value_type *phrase)
317{
318 const auto phraseSize = std::strlen(phrase);
319 if (str.size() < phraseSize) {
320 return false;
321 }
322 for (auto stri = str.cend() - static_cast<typename StringType::difference_type>(phraseSize), strend = str.cend(); stri != strend;
323 ++stri, ++phrase) {
324 if (*stri != *phrase) {
325 return false;
326 }
327 }
328 return true;
329}
330
335template <typename StringType> bool containsSubstrings(const StringType &str, std::initializer_list<StringType> substrings)
336{
337 typename StringType::size_type currentPos = 0;
338 for (const auto &substr : substrings) {
339 if ((currentPos = str.find(substr, currentPos)) == StringType::npos) {
340 return false;
341 }
342 currentPos += substr.size();
343 }
344 return true;
345}
346
351template <typename StringType>
352bool containsSubstrings(const StringType &str, std::initializer_list<const typename StringType::value_type *> substrings)
353{
354 typename StringType::size_type currentPos = 0;
355 for (const auto *substr : substrings) {
356 if ((currentPos = str.find(substr, currentPos)) == StringType::npos) {
357 return false;
358 }
359 currentPos += std::strlen(substr);
360 }
361 return true;
362}
363
367template <typename StringType1, typename StringType2, typename StringType3>
369{
370 for (typename StringType1::size_type i = 0; (i = str.find(find, i)) != StringType1::npos; i += replace.size()) {
371 str.replace(i, find.size(), replace);
372 }
373}
374
378template <typename StringType>
379inline void findAndReplace(StringType &str, const typename StringType::value_type *find, const typename StringType::value_type *replace)
380{
382 str, std::basic_string_view<typename StringType::value_type>(find), std::basic_string_view<typename StringType::value_type>(replace));
383}
384
388template <typename StringType1, typename StringType2>
389inline void findAndReplace(StringType1 &str, const StringType2 &find, const typename StringType1::value_type *replace)
390{
391 findAndReplace(str, find, std::basic_string_view<typename StringType1::value_type>(replace));
392}
393
397template <typename StringType1, typename StringType2>
398inline void findAndReplace(StringType1 &str, const typename StringType1::value_type *find, const StringType2 &replace)
399{
400 findAndReplace(str, std::basic_string_view<typename StringType1::value_type>(find), replace);
401}
402
409template <typename CharType> constexpr CharType digitToChar(CharType digit)
410{
411 return digit <= 9 ? (digit + '0') : (digit + 'A' - 10);
412}
413
420template <typename IntegralType, class StringType = std::string, typename BaseType = IntegralType,
421 CppUtilities::Traits::EnableIf<std::is_integral<IntegralType>, std::is_unsigned<IntegralType>> * = nullptr>
423{
424 auto resSize = std::size_t();
425 auto n = number;
426 do {
427 n /= static_cast<IntegralType>(base), ++resSize;
428 } while (n);
429 auto res = StringType(resSize, typename StringType::value_type());
430 auto resIter = res.end();
431 do {
432 *(--resIter)
433 = digitToChar<typename StringType::value_type>(static_cast<typename StringType::value_type>(number % static_cast<IntegralType>(base)));
434 number /= static_cast<IntegralType>(base);
435 } while (number);
436 return res;
437}
438
445template <typename IntegralType, class StringType = std::string, typename BaseType = IntegralType,
446 Traits::EnableIf<std::is_integral<IntegralType>, std::is_signed<IntegralType>> * = nullptr>
448{
449 const auto negative = number < 0;
450 auto resSize = std::size_t();
451 if (negative) {
452 number = -number, resSize = 1;
453 }
454 auto n = number;
455 do {
456 n /= static_cast<IntegralType>(base), ++resSize;
457 } while (n);
458 auto res = StringType(resSize, typename StringType::value_type());
459 auto resIter = res.end();
460 do {
461 *(--resIter)
462 = digitToChar<typename StringType::value_type>(static_cast<typename StringType::value_type>(number % static_cast<IntegralType>(base)));
463 number /= static_cast<IntegralType>(base);
464 } while (number);
465 if (negative) {
466 *(--resIter) = '-';
467 }
468 return res;
469}
470
479template <typename FloatingType, class StringType = std::string, Traits::EnableIf<std::is_floating_point<FloatingType>> * = nullptr>
481{
482 std::basic_stringstream<typename StringType::value_type> ss;
483 ss << std::setbase(base) << number;
484 return ss.str();
485}
486
491template <typename CharType> CharType charToDigit(CharType character, CharType base)
492{
493 auto res = base;
494 if (character >= '0' && character <= '9') {
495 res = character - '0';
496 } else if (character >= 'a' && character <= 'z') {
497 res = character - 'a' + 10;
498 } else if (character >= 'A' && character <= 'Z') {
499 res = character - 'A' + 10;
500 }
501 if (res < base) {
502 return res;
503 }
504 constexpr auto msgBegin = std::string_view("The character \"");
505 constexpr auto msgEnd = std::string_view("\" is no valid digit.");
506 auto errorMsg = std::string();
507 errorMsg.reserve(msgBegin.size() + msgEnd.size() + 2);
509 errorMsg += character >= ' ' && character <= '~' ? static_cast<std::string::value_type>(character) : '?';
510 errorMsg += msgEnd;
511 throw ConversionException(std::move(errorMsg));
512}
513
515namespace Detail {
516template <typename IntegralType, typename CharType, typename BaseType = IntegralType>
517void raiseAndAdd(IntegralType &result, BaseType base, CharType character)
518{
519 if (character == ' ') {
520 return;
521 }
522#ifdef __GNUC__ // overflow detection only supported on GCC and Clang
523 if (__builtin_mul_overflow(result, base, &result)
524 || __builtin_add_overflow(result, charToDigit(character, static_cast<CharType>(base)), &result)) {
525 throw ConversionException("Number exceeds limit.");
526 }
527#else
528 result *= static_cast<IntegralType>(base);
529 result += static_cast<IntegralType>(charToDigit<CharType>(character, static_cast<CharType>(base)));
530#endif
531}
532} // namespace Detail
534
542template <typename IntegralType, class CharType, typename BaseType = IntegralType,
543 Traits::EnableIf<std::is_integral<IntegralType>, std::is_unsigned<IntegralType>> * = nullptr>
544IntegralType bufferToNumber(const CharType *string, std::size_t size, BaseType base = 10)
545{
547 for (const CharType *end = string + size; string != end; ++string) {
548 Detail::raiseAndAdd(result, base, *string);
549 }
550 return result;
551}
552
560template <typename IntegralType, typename CharType, typename BaseType = IntegralType,
561 Traits::EnableIf<std::is_integral<IntegralType>, std::is_signed<IntegralType>> * = nullptr>
562IntegralType bufferToNumber(const CharType *string, std::size_t size, BaseType base = 10)
563{
564 if (!size) {
565 return 0;
566 }
567 const CharType *end = string + size;
568 for (; string != end && *string == ' '; ++string)
569 ;
570 if (string == end) {
571 return 0;
572 }
573 const bool negative = (*string == '-');
574 if (negative) {
575 ++string;
576 }
578 for (; string != end; ++string) {
579 Detail::raiseAndAdd(result, base, *string);
580 }
581 return negative ? -result : result;
582}
583
591template <typename IntegralType, class StringType, typename BaseType = IntegralType,
592 Traits::EnableIf<std::is_integral<IntegralType>, Traits::Not<std::is_scalar<std::decay_t<StringType>>>> * = nullptr>
597
607template <typename FloatingType, class StringViewType,
608 Traits::EnableIf<std::is_floating_point<FloatingType>, Traits::IsSpecializationOf<StringViewType, std::basic_string_view>> * = nullptr>
610{
611 std::basic_stringstream<typename StringViewType::value_type> ss;
612 ss << std::setbase(base) << stringView;
614 if ((ss >> result) && ss.eof()) {
615 return result;
616 }
617 std::string errorMsg;
618 errorMsg.reserve(48 + stringView.size());
619 errorMsg += "The string \"";
621 errorMsg += "\" is no valid floating point number.";
623}
624
634template <typename FloatingType, class StringType,
635 Traits::EnableIf<std::is_floating_point<FloatingType>, Traits::Not<std::is_scalar<std::decay_t<StringType>>>,
636 Traits::Not<Traits::IsSpecializationOf<StringType, std::basic_string_view>>> * = nullptr>
638{
639 using StringViewType = std::basic_string_view<typename StringType::value_type>;
641}
642
650template <typename IntegralType, typename CharType, typename BaseType = IntegralType,
651 Traits::EnableIf<std::is_integral<IntegralType>, std::is_unsigned<IntegralType>> * = nullptr>
653{
655 for (; *string; ++string) {
656 Detail::raiseAndAdd(result, base, *string);
657 }
658 return result;
659}
660
670template <typename FloatingType, class CharType, Traits::EnableIf<std::is_floating_point<FloatingType>> * = nullptr>
675
683template <typename IntegralType, typename CharType, typename BaseType = IntegralType,
684 Traits::EnableIf<std::is_integral<IntegralType>, std::is_signed<IntegralType>> * = nullptr>
686{
687 if (!*string) {
688 return 0;
689 }
690 for (; *string && *string == ' '; ++string)
691 ;
692 if (!*string) {
693 return 0;
694 }
695 const bool negative = (*string == '-');
696 if (negative) {
697 ++string;
698 }
700 for (; *string; ++string) {
701 Detail::raiseAndAdd(result, base, *string);
702 }
703 return negative ? -result : result;
704}
705
715template <typename T> std::string interpretIntegerAsString(T integer, int startOffset = 0)
716{
717 char buffer[sizeof(T)];
718 BE::getBytes(integer, buffer);
719 return std::string(buffer + startOffset, sizeof(T) - static_cast<std::size_t>(startOffset));
720}
721
722CPP_UTILITIES_EXPORT std::string dataSizeToString(std::uint64_t sizeInByte, bool includeByte = false);
724CPP_UTILITIES_EXPORT std::string encodeBase64(const std::uint8_t *data, std::uint32_t dataSize);
725CPP_UTILITIES_EXPORT std::pair<std::unique_ptr<std::uint8_t[]>, std::uint32_t> decodeBase64(const char *encodedStr, const std::uint32_t strSize);
726} // namespace CppUtilities
727
728#endif // CONVERSION_UTILITIES_STRINGCONVERSION_H
The ConversionException class is thrown by the various conversion functions of this library when a co...
#define CPP_UTILITIES_EXPORT
Marks the symbol to be exported by the c++utilities library.
Definition global.h:14
typename std::enable_if< All< Condition... >::value, Detail::Enabler >::type EnableIf
Shortcut for std::enable_if to omit ::value and ::type.
Definition traits.h:44
Contains all utilities provides by the c++utilities library.
void findAndReplace(StringType1 &str, const StringType2 &find, const StringType3 &replace)
Replaces all occurrences of find with relpace in the specified str.
CPP_UTILITIES_EXPORT StringData convertUtf8ToUtf16BE(const char *inputBuffer, std::size_t inputBufferSize)
Converts the specified UTF-8 string to UTF-16 (big-endian).
CPP_UTILITIES_EXPORT StringData convertString(const char *fromCharset, const char *toCharset, const char *inputBuffer, std::size_t inputBufferSize, float outputBufferSizeFactor=1.0f)
Converts the specified string from one character set to another.
std::pair< std::unique_ptr< char[], StringDataDeleter >, std::size_t > StringData
Type used to return string encoding conversion result.
StringType numberToString(IntegralType number, BaseType base=10)
Converts the given number to its equivalent string representation using the specified base.
CPP_UTILITIES_EXPORT StringData convertLatin1ToUtf8(const char *inputBuffer, std::size_t inputBufferSize)
Converts the specified Latin-1 string to UTF-8.
CPP_UTILITIES_EXPORT StringData convertUtf16LEToUtf8(const char *inputBuffer, std::size_t inputBufferSize)
Converts the specified UTF-16 (little-endian) string to UTF-8.
EmptyPartsTreat
Specifies the role of empty parts when splitting strings.
CPP_UTILITIES_EXPORT std::pair< std::unique_ptr< std::uint8_t[]>, std::uint32_t > decodeBase64(const char *encodedStr, const std::uint32_t strSize)
Decodes the specified Base64 encoded string.
ReturnType joinStrings(const Container &strings, Detail::StringParamForContainer< Container > delimiter=Detail::StringParamForContainer< Container >(), bool omitEmpty=false, Detail::StringParamForContainer< Container > leftClosure=Detail::StringParamForContainer< Container >(), Detail::StringParamForContainer< Container > rightClosure=Detail::StringParamForContainer< Container >())
Joins the given strings using the specified delimiter.
bool startsWith(const StringType &str, const StringType &phrase)
Returns whether str starts with phrase.
IntegralType stringToNumber(const StringType &string, BaseType base=10)
Converts the given string to an unsigned/signed number assuming string uses the specified base.
CPP_UTILITIES_EXPORT void truncateString(std::string &str, char terminationChar='\0')
Truncates all characters after the first occurrence of the specified terminationChar and the terminat...
CPP_UTILITIES_EXPORT StringData convertUtf16BEToUtf8(const char *inputBuffer, std::size_t inputBufferSize)
Converts the specified UTF-16 (big-endian) string to UTF-8.
constexpr CharType digitToChar(CharType digit)
Returns the character representation of the specified digit.
Container splitStringSimple(Detail::StringParamForContainer< Container > string, Detail::StringParamForContainer< Container > delimiter, int maxParts=-1)
Splits the given string (which might also be a string view) at the specified delimiter.
auto toArrayOfLines(const std::string &multilineString)
Converts the specified multilineString to an array of lines.
bool containsSubstrings(const StringType &str, std::initializer_list< StringType > substrings)
Returns whether str contains the specified substrings.
CPP_UTILITIES_EXPORT StringData convertUtf8ToLatin1(const char *inputBuffer, std::size_t inputBufferSize)
Converts the specified UTF-8 string to Latin-1.
std::string interpretIntegerAsString(T integer, int startOffset=0)
Interprets the given integer at the specified position as std::string using the specified byte order.
bool endsWith(const StringType &str, const StringType &phrase)
Returns whether str ends with phrase.
auto toMultiline(const Container &arrayOfLines)
Converts the specified arrayOfLines to a multiline string.
CPP_UTILITIES_EXPORT StringData convertUtf8ToUtf16LE(const char *inputBuffer, std::size_t inputBufferSize)
Converts the specified UTF-8 string to UTF-16 (little-endian).
CPP_UTILITIES_EXPORT std::string bitrateToString(double speedInKbitsPerSecond, bool useByteInsteadOfBits=false)
Converts the specified bitrate in kbit/s to its equivalent std::string representation.
CharType charToDigit(CharType character, CharType base)
Returns number/digit of the specified character representation using the specified base.
CPP_UTILITIES_EXPORT std::string encodeBase64(const std::uint8_t *data, std::uint32_t dataSize)
Encodes the specified data to Base64.
CPP_UTILITIES_EXPORT std::string dataSizeToString(std::uint64_t sizeInByte, bool includeByte=false)
Converts the specified data size in byte to its equivalent std::string representation.
IntegralType bufferToNumber(const CharType *string, std::size_t size, BaseType base=10)
Converts the given string of size characters to an unsigned numeric value using the specified base.
Container splitString(Detail::StringParamForContainer< Container > string, Detail::StringParamForContainer< Container > delimiter, EmptyPartsTreat emptyPartsRole=EmptyPartsTreat::Keep, int maxParts=-1)
Splits the given string at the specified delimiter.
The StringDataDeleter struct deletes the data of a StringData instance.
void operator()(char *stringData)
Deletes the specified stringData with std::free(), because the memory has been allocated using std::m...
constexpr int i