Make numberToString/stringToNumber work with u16string

Don't rely on std::basic_stringstream at least for
integral types.
This commit is contained in:
Martchus 2016-11-13 23:06:03 +01:00
parent bd9cdfccb2
commit 2be349856f
2 changed files with 217 additions and 26 deletions

View File

@ -4,6 +4,8 @@
#include "./conversionexception.h"
#include "./binaryconversion.h"
#include "../misc/traits.h"
#include <string>
#include <cstring>
#include <sstream>
@ -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 <class Container = std::initializer_list<std::string> > CPP_UTILITIES_EXPORT
template <class Container = std::initializer_list<std::string> >
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 <class Container = std::list<std::string> > CPP_UTILITIES_EXPORT
template <class Container = std::list<std::string> >
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 <typename StringType> CPP_UTILITIES_EXPORT bool startsWith(const StringType &str, const StringType &phrase)
template <typename StringType>
bool startsWith(const StringType &str, const StringType &phrase)
{
if(str.size() < phrase.size()) {
return false;
@ -166,7 +169,8 @@ template <typename StringType> CPP_UTILITIES_EXPORT bool startsWith(const String
/*!
* \brief Returns whether \a str starts with \a phrase.
*/
template <typename StringType> CPP_UTILITIES_EXPORT bool startsWith(const StringType &str, const typename StringType::value_type *phrase)
template <typename StringType>
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 <typename StringType> 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 <typename StringType> CPP_UTILITIES_EXPORT bool containsSubstrings(const StringType &str, std::initializer_list<StringType> substrings)
template <typename StringType>
bool containsSubstrings(const StringType &str, std::initializer_list<StringType> substrings)
{
typename StringType::size_type currentPos = 0;
for(const auto &substr : substrings) {
@ -198,7 +203,8 @@ template <typename StringType> 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 <typename StringType> CPP_UTILITIES_EXPORT bool containsSubstrings(const StringType &str, std::initializer_list<const typename StringType::value_type *> substrings)
template <typename StringType>
bool containsSubstrings(const StringType &str, std::initializer_list<const typename StringType::value_type *> substrings)
{
typename StringType::size_type currentPos = 0;
for(const auto *substr : substrings) {
@ -213,42 +219,188 @@ template <typename StringType> CPP_UTILITIES_EXPORT bool containsSubstrings(cons
/*!
* \brief Replaces all occurences of \a find with \a relpace in the specified \a str.
*/
template <typename StringType> CPP_UTILITIES_EXPORT void findAndReplace(StringType &str, const StringType &find, const StringType &replace)
template <typename StringType>
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 <typename CharType>
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 <typename NumberType, typename StringType = std::string> CPP_UTILITIES_EXPORT StringType numberToString(NumberType number, int base = 10)
template <typename IntegralType, class StringType = std::string, Traits::EnableIf<std::is_integral<IntegralType>, Traits::Not<std::is_signed<IntegralType> > >...>
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<typename StringType::value_type>(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 <typename IntegralType, class StringType = std::string, Traits::EnableIf<std::is_integral<IntegralType>, std::is_signed<IntegralType> >...>
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<typename StringType::value_type>(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 <typename FloatingType, class StringType = std::string, Traits::EnableIf<std::is_floating_point<FloatingType> >...>
StringType numberToString(FloatingType number, typename StringType::value_type base = 10)
{
std::basic_stringstream<typename StringType::value_type> 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 <typename CharType>
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 <typename NumberType, typename StringType> CPP_UTILITIES_EXPORT NumberType stringToNumber(const StringType &string, int base = 10)
template <typename IntegralType, class StringType, Traits::EnableIf<std::is_integral<IntegralType>, Traits::Not<std::is_signed<IntegralType> > >...>
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<typename StringType::value_type>(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 <typename IntegralType, class StringType, Traits::EnableIf<std::is_integral<IntegralType>, std::is_signed<IntegralType> >...>
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<typename StringType::value_type>(*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 <typename FloatingType, class StringType, Traits::EnableIf<std::is_floating_point<FloatingType> >...>
FloatingType stringToNumber(const StringType &string, typename StringType::value_type base = 10)
{
std::basic_stringstream<typename StringType::value_type> 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 <typename NumberType, typename StringType> 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 <typename NumberType, typename CharType> CPP_UTILITIES_EXPORT NumberType stringToNumber(const CharType *string, int base = 10)
template <typename IntegralType, class CharType, Traits::EnableIf<std::is_integral<IntegralType>, Traits::Not<std::is_signed<IntegralType> > >...>
IntegralType stringToNumber(const CharType *string, unsigned char base = 10)
{
std::basic_stringstream<CharType> 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<CharType>(*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 <typename IntegralType, class CharType, Traits::EnableIf<std::is_integral<IntegralType>, std::is_signed<IntegralType> >...>
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<CharType>(*string, base);
}
return negative ? -result : result;
}
/*!
@ -280,7 +462,8 @@ template <typename NumberType, typename CharType> CPP_UTILITIES_EXPORT NumberTyp
*
* \tparam T The data type of the integer to be interpreted.
*/
template <typename T> CPP_UTILITIES_EXPORT std::string interpretIntegerAsString(T integer, int startOffset = 0)
template <typename T>
std::string interpretIntegerAsString(T integer, int startOffset = 0)
{
char buffer[sizeof(T)];
ConversionUtilities::BE::getBytes(integer, buffer);

View File

@ -237,17 +237,25 @@ void ConversionTests::testStringConversions()
for(const auto base : initializer_list<byte>{2, 8, 10, 16}) {
auto resultString = stringToNumber<uint64, string>(numberToString<uint64, string>(unsignedRandom, base), base);
auto resultWideString = stringToNumber<uint64, wstring>(numberToString<uint64, wstring>(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<byte>{10}) {
auto resultString = stringToNumber<int64, string>(numberToString<int64, string>(signedRandom, base), base);
auto resultWideString = stringToNumber<int64, wstring>(numberToString<int64, wstring>(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<int32, string>("01");
CPPUNIT_ASSERT_EQUAL(1, res);
res = stringToNumber<int32, wstring>(L"01");
CPPUNIT_ASSERT_EQUAL(1, res);
res = stringToNumber<int32, u16string>(u"01");
CPPUNIT_ASSERT_EQUAL(1, res);
// interpretIntegerAsString()
CPPUNIT_ASSERT(interpretIntegerAsString<uint32>(0x54455354) == "TEST");