From 2be349856f5701271ab9759644cd393d8b2372c2 Mon Sep 17 00:00:00 2001 From: Martchus Date: Sun, 13 Nov 2016 23:06:03 +0100 Subject: [PATCH] Make numberToString/stringToNumber work with u16string Don't rely on std::basic_stringstream at least for integral types. --- conversion/stringconversion.h | 227 ++++++++++++++++++++++++++++++---- tests/conversiontests.cpp | 16 ++- 2 files changed, 217 insertions(+), 26 deletions(-) diff --git a/conversion/stringconversion.h b/conversion/stringconversion.h index 0de64fa..5dc8299 100644 --- a/conversion/stringconversion.h +++ b/conversion/stringconversion.h @@ -4,6 +4,8 @@ #include "./conversionexception.h" #include "./binaryconversion.h" +#include "../misc/traits.h" + #include #include #include @@ -59,7 +61,7 @@ CPP_UTILITIES_EXPORT void truncateString(std::string &str, char terminationChar * \tparam Container The STL-container used to provide the \a strings. * \returns Returns the joined string. */ -template > CPP_UTILITIES_EXPORT +template > typename Container::value_type joinStrings(const Container &strings, const typename Container::value_type &delimiter = typename Container::value_type(), bool omitEmpty = false, const typename Container::value_type &leftClosure = typename Container::value_type(), const typename Container::value_type &rightClosure = typename Container::value_type()) { typename Container::value_type res; @@ -108,7 +110,7 @@ enum class EmptyPartsTreat * \tparam Container The STL-container used to return the parts. * \returns Returns the parts. */ -template > CPP_UTILITIES_EXPORT +template > Container splitString(const typename Container::value_type &string, const typename Container::value_type &delimiter, EmptyPartsTreat emptyPartsRole = EmptyPartsTreat::Keep, int maxParts = -1) { --maxParts; @@ -148,7 +150,8 @@ Container splitString(const typename Container::value_type &string, const typena /*! * \brief Returns whether \a str starts with \a phrase. */ -template CPP_UTILITIES_EXPORT bool startsWith(const StringType &str, const StringType &phrase) +template +bool startsWith(const StringType &str, const StringType &phrase) { if(str.size() < phrase.size()) { return false; @@ -166,7 +169,8 @@ template CPP_UTILITIES_EXPORT bool startsWith(const String /*! * \brief Returns whether \a str starts with \a phrase. */ -template CPP_UTILITIES_EXPORT bool startsWith(const StringType &str, const typename StringType::value_type *phrase) +template +bool startsWith(const StringType &str, const typename StringType::value_type *phrase) { for(auto stri = str.cbegin(), strend = str.cend(); stri != strend; ++stri, ++phrase) { if(!*phrase) { @@ -182,7 +186,8 @@ template CPP_UTILITIES_EXPORT bool startsWith(const String * \brief Returns whether \a str contains the specified \a substrings. * \remarks The \a substrings must occur in the specified order. */ -template CPP_UTILITIES_EXPORT bool containsSubstrings(const StringType &str, std::initializer_list substrings) +template +bool containsSubstrings(const StringType &str, std::initializer_list substrings) { typename StringType::size_type currentPos = 0; for(const auto &substr : substrings) { @@ -198,7 +203,8 @@ template CPP_UTILITIES_EXPORT bool containsSubstrings(cons * \brief Returns whether \a str contains the specified \a substrings. * \remarks The \a substrings must occur in the specified order. */ -template CPP_UTILITIES_EXPORT bool containsSubstrings(const StringType &str, std::initializer_list substrings) +template +bool containsSubstrings(const StringType &str, std::initializer_list substrings) { typename StringType::size_type currentPos = 0; for(const auto *substr : substrings) { @@ -213,42 +219,188 @@ template CPP_UTILITIES_EXPORT bool containsSubstrings(cons /*! * \brief Replaces all occurences of \a find with \a relpace in the specified \a str. */ -template CPP_UTILITIES_EXPORT void findAndReplace(StringType &str, const StringType &find, const StringType &replace) +template +void findAndReplace(StringType &str, const StringType &find, const StringType &replace) { for(typename StringType::size_type i = 0; (i = str.find(find, i)) != StringType::npos; i += replace.size()) { str.replace(i, find.size(), replace); } } +/*! + * \brief Returns the character representation of the specified \a digit. + * \remarks + * - Uses capital letters. + * - Valid values for \a digit: 0 <= \a digit <= 35 + */ +template +CharType digitToChar(CharType digit) +{ + CharType res; + if(digit <= 9) { + res = digit + '0'; + } else { + res = digit + 'A' - 10; + } + return res; +} + /*! * \brief Converts the given \a number to its equivalent string representation using the specified \a base. * \tparam NumberType The data type of the given number. * \tparam StringType The string type (should be an instantiation of the basic_string class template). * \sa stringToNumber() */ -template CPP_UTILITIES_EXPORT StringType numberToString(NumberType number, int base = 10) +template , Traits::Not > >...> +StringType numberToString(IntegralType number, typename StringType::value_type base = 10) +{ + std::size_t resSize = 0; + for(auto n = number; n; n /= base, ++resSize); + StringType res; + res.reserve(resSize); + for(; number; number /= base) { + res.insert(res.begin(), digitToChar(number % base)); + } + return res; +} + +/*! + * \brief Converts the given \a number to its equivalent string representation using the specified \a base. + * \tparam NumberType The data type of the given number. + * \tparam StringType The string type (should be an instantiation of the basic_string class template). + * \sa stringToNumber() + */ +template , std::is_signed >...> +StringType numberToString(IntegralType number, typename StringType::value_type base = 10) +{ + const bool negative = number < 0; + std::size_t resSize; + if(negative) { + number = -number, resSize = 1; + } else { + resSize = 0; + } + for(auto n = number; n; n /= base, ++resSize); + StringType res; + res.reserve(resSize); + for(; number; number /= base) { + res.insert(res.begin(), digitToChar(number % base)); + } + if(negative) { + res.insert(res.begin(), '-'); + } + return res; +} + +/*! + * \brief Converts the given \a number to its equivalent string representation using the specified \a base. + * \tparam NumberType The data type of the given number. + * \tparam StringType The string type (should be an instantiation of the basic_string class template). + * \remarks This function is using std::basic_stringstream interanlly and hence also has its limitations (eg. regarding + * \a base and types). + * \sa stringToNumber() + */ +template >...> +StringType numberToString(FloatingType number, typename StringType::value_type base = 10) { std::basic_stringstream ss; ss << std::setbase(base) << number; return ss.str(); } +/*! + * \brief Returns number/digit of the specified \a character representation using the specified \a base. + * \throws A ConversionException will be thrown if the provided \a character does not represent a valid digit for the specified \a base. + */ +template +CharType charToDigit(CharType character, CharType base) +{ + CharType res; + if(character >= '0' && character <= '9') { + res = character - '0'; + } else if(character >= 'a' && character <= 'z') { + res = character - 'a' + 10; + } else if(character >= 'A' && character <= 'Z') { + res = character - 'A' + 10; + } else { + throw ConversionException("The string is no valid number"); + } + if(res >= base) { + throw ConversionException("The string is no valid number"); + } + return res; +} + /*! * \brief Converts the given \a string to a number assuming \a string uses the specified \a base. * \tparam NumberType The data type used to store the converted value. * \tparam StringType The string type (should be an instantiation of the basic_string class template). - * \throws A ConversionException will be thrown if the provided string is not a valid number. + * \throws A ConversionException will be thrown if the provided \a string is not a valid number. * \sa numberToString() */ -template CPP_UTILITIES_EXPORT NumberType stringToNumber(const StringType &string, int base = 10) +template , Traits::Not > >...> +IntegralType stringToNumber(const StringType &string, typename StringType::value_type base = 10) +{ + IntegralType result = 0; + for(const auto &c : string) { + if(c == ' ') { + continue; + } + result *= base; + result += charToDigit(c, base); + } + return result; +} + +/*! + * \brief Converts the given \a string to a number assuming \a string uses the specified \a base. + * \tparam NumberType The data type used to store the converted value. + * \tparam StringType The string type (should be an instantiation of the basic_string class template). + * \throws A ConversionException will be thrown if the provided \a string is not a valid number. + * \sa numberToString() + */ +template , std::is_signed >...> +IntegralType stringToNumber(const StringType &string, typename StringType::value_type base = 10) +{ + auto i = string.begin(); + auto end = string.end(); + if(i == end) { + return 0; + } + const bool negative = (*i == '-'); + if(negative) { + ++i; + } + IntegralType result = 0; + for(; i != end; ++i) { + if(*i == ' ') { + continue; + } + result *= base; + result += charToDigit(*i, base); + } + return negative ? -result : result; +} + +/*! + * \brief Converts the given \a string to a number assuming \a string uses the specified \a base. + * \tparam NumberType The data type used to store the converted value. + * \tparam StringType The string type (should be an instantiation of the basic_string class template). + * \throws A ConversionException will be thrown if the provided \a string is not a valid number. + * \remarks This function is using std::basic_stringstream interanlly and hence also has its limitations (eg. regarding + * \a base and types). + * \sa numberToString() + */ +template >...> +FloatingType stringToNumber(const StringType &string, typename StringType::value_type base = 10) { std::basic_stringstream ss; ss << std::setbase(base) << string; - NumberType result; + FloatingType result; if((ss >> result) && ss.eof()) { return result; } else { - throw ConversionException("The specified string is no valid number."); + throw ConversionException("The string is no valid number."); } } @@ -256,19 +408,49 @@ template CPP_UTILITIES_EXPORT NumberT * \brief Converts the given null-terminated \a string to a numeric value using the specified \a base. * \tparam NumberType The data type used to store the converted value. * \tparam StringType The string type (should be an instantiation of the basic_string class template). - * \throws A ConversionException will be thrown if the provided string is not a valid number. + * \throws A ConversionException will be thrown if the provided \a string is not a valid number. * \sa numberToString() */ -template CPP_UTILITIES_EXPORT NumberType stringToNumber(const CharType *string, int base = 10) +template , Traits::Not > >...> +IntegralType stringToNumber(const CharType *string, unsigned char base = 10) { - std::basic_stringstream ss; - ss << std::setbase(base) << string; - NumberType result; - if((ss >> result) && ss.eof()) { - return result; - } else { - throw ConversionException("The specified string is no valid number."); + IntegralType result = 0; + for(; *string; ++string) { + if(*string == ' ') { + continue; + } + result *= base; + result += charToDigit(*string, base); } + return result; +} + +/*! + * \brief Converts the given null-terminated \a string to a numeric value using the specified \a base. + * \tparam NumberType The data type used to store the converted value. + * \tparam StringType The string type (should be an instantiation of the basic_string class template). + * \throws A ConversionException will be thrown if the provided \a string is not a valid number. + * \sa numberToString() + */ +template , std::is_signed >...> +IntegralType stringToNumber(const CharType *string, unsigned char base = 10) +{ + if(!*string) { + return 0; + } + const bool negative = (*string == '-'); + if(negative) { + ++string; + } + IntegralType result = 0; + for(; *string; ++string) { + if(*string == ' ') { + continue; + } + result *= base; + result += charToDigit(*string, base); + } + return negative ? -result : result; } /*! @@ -280,7 +462,8 @@ template CPP_UTILITIES_EXPORT NumberTyp * * \tparam T The data type of the integer to be interpreted. */ -template CPP_UTILITIES_EXPORT std::string interpretIntegerAsString(T integer, int startOffset = 0) +template +std::string interpretIntegerAsString(T integer, int startOffset = 0) { char buffer[sizeof(T)]; ConversionUtilities::BE::getBytes(integer, buffer); diff --git a/tests/conversiontests.cpp b/tests/conversiontests.cpp index 4a97863..7bd9df7 100644 --- a/tests/conversiontests.cpp +++ b/tests/conversiontests.cpp @@ -237,17 +237,25 @@ void ConversionTests::testStringConversions() for(const auto base : initializer_list{2, 8, 10, 16}) { auto resultString = stringToNumber(numberToString(unsignedRandom, base), base); auto resultWideString = stringToNumber(numberToString(unsignedRandom, base), base); - CPPUNIT_ASSERT(resultString == unsignedRandom); - CPPUNIT_ASSERT(resultWideString == unsignedRandom); + CPPUNIT_ASSERT_EQUAL(unsignedRandom, resultString); + CPPUNIT_ASSERT_EQUAL(unsignedRandom, resultWideString); } for(const auto base : initializer_list{10}) { auto resultString = stringToNumber(numberToString(signedRandom, base), base); auto resultWideString = stringToNumber(numberToString(signedRandom, base), base); - CPPUNIT_ASSERT(resultString == signedRandom); - CPPUNIT_ASSERT(resultWideString == signedRandom); + CPPUNIT_ASSERT_EQUAL(signedRandom, resultString); + CPPUNIT_ASSERT_EQUAL(signedRandom, resultWideString); } } + // stringToNumber() with leading zeroes and different types + int32 res = stringToNumber("01"); + CPPUNIT_ASSERT_EQUAL(1, res); + res = stringToNumber(L"01"); + CPPUNIT_ASSERT_EQUAL(1, res); + res = stringToNumber(u"01"); + CPPUNIT_ASSERT_EQUAL(1, res); + // interpretIntegerAsString() CPPUNIT_ASSERT(interpretIntegerAsString(0x54455354) == "TEST");