Support unsigned integers in TagValue
This commit is contained in:
parent
dcda6b673b
commit
08570aea32
113
tagvalue.cpp
113
tagvalue.cpp
|
@ -266,8 +266,6 @@ bool TagValue::compareTo(const TagValue &other, TagValueComparisionFlags options
|
|||
}
|
||||
case TagDataType::PositionInSet:
|
||||
return toPositionInSet() == other.toPositionInSet();
|
||||
case TagDataType::Integer:
|
||||
return toInteger() == other.toInteger();
|
||||
case TagDataType::StandardGenreIndex:
|
||||
return toStandardGenreIndex() == other.toStandardGenreIndex();
|
||||
case TagDataType::TimeSpan:
|
||||
|
@ -298,7 +296,11 @@ bool TagValue::compareTo(const TagValue &other, TagValueComparisionFlags options
|
|||
}
|
||||
|
||||
// handle types where an implicit conversion to the specific type can be done
|
||||
if (m_type == TagDataType::Popularity || other.m_type == TagDataType::Popularity) {
|
||||
if (m_type == TagDataType::Integer || other.m_type == TagDataType::Integer) {
|
||||
return toInteger() == other.toInteger();
|
||||
} else if (m_type == TagDataType::UnsignedInteger || other.m_type == TagDataType::UnsignedInteger) {
|
||||
return toUnsignedInteger() == other.toUnsignedInteger();
|
||||
} else if (m_type == TagDataType::Popularity || other.m_type == TagDataType::Popularity) {
|
||||
if (options & TagValueComparisionFlags::CaseInsensitive) {
|
||||
const auto lhs = toPopularity(), rhs = other.toPopularity();
|
||||
return lhs.rating == rhs.rating && lhs.playCounter == rhs.playCounter && compareData(lhs.user, rhs.user, true);
|
||||
|
@ -391,6 +393,51 @@ std::int32_t TagValue::toInteger() const
|
|||
throw ConversionException("Can not convert assigned data to integer because the data size is not appropriate.");
|
||||
case TagDataType::Popularity:
|
||||
return static_cast<std::int32_t>(toPopularity().rating);
|
||||
case TagDataType::UnsignedInteger: {
|
||||
const auto unsignedInteger = toUnsignedInteger();
|
||||
if (unsignedInteger > std::numeric_limits<std::int32_t>::max()) {
|
||||
throw ConversionException(argsToString("Unsigned integer too big for conversion to integer."));
|
||||
}
|
||||
return static_cast<std::int32_t>(unsignedInteger);
|
||||
}
|
||||
default:
|
||||
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t TagValue::toUnsignedInteger() const
|
||||
{
|
||||
if (isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
switch (m_type) {
|
||||
case TagDataType::Text:
|
||||
switch (m_encoding) {
|
||||
case TagTextEncoding::Utf16LittleEndian:
|
||||
case TagTextEncoding::Utf16BigEndian: {
|
||||
auto u16str = u16string(reinterpret_cast<char16_t *>(m_ptr.get()), m_size / 2);
|
||||
ensureHostByteOrder(u16str, m_encoding);
|
||||
return stringToNumber<std::uint64_t>(u16str);
|
||||
}
|
||||
default:
|
||||
return bufferToNumber<std::uint64_t>(m_ptr.get(), m_size);
|
||||
}
|
||||
case TagDataType::PositionInSet:
|
||||
case TagDataType::Integer:
|
||||
case TagDataType::StandardGenreIndex: {
|
||||
const auto integer = toInteger();
|
||||
if (integer < 0) {
|
||||
throw ConversionException(argsToString("Can not convert negative value to unsigned integer."));
|
||||
}
|
||||
return static_cast<std::uint64_t>(integer);
|
||||
}
|
||||
case TagDataType::Popularity:
|
||||
return static_cast<std::uint64_t>(toPopularity().rating);
|
||||
case TagDataType::UnsignedInteger:
|
||||
if (m_size == sizeof(std::uint64_t)) {
|
||||
return *reinterpret_cast<std::uint64_t *>(m_ptr.get());
|
||||
}
|
||||
throw ConversionException("Can not convert assigned data to unsigned integer because the data size is not appropriate.");
|
||||
default:
|
||||
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to integer."));
|
||||
}
|
||||
|
@ -423,10 +470,19 @@ int TagValue::toStandardGenreIndex() const
|
|||
}
|
||||
case TagDataType::StandardGenreIndex:
|
||||
case TagDataType::Integer:
|
||||
if (m_size != sizeof(std::int32_t)) {
|
||||
case TagDataType::UnsignedInteger:
|
||||
if (m_size == sizeof(std::int32_t)) {
|
||||
index = static_cast<int>(*reinterpret_cast<std::int32_t *>(m_ptr.get()));
|
||||
} else if (m_size == sizeof(std::uint64_t)) {
|
||||
const auto unsignedInt = *reinterpret_cast<std::uint64_t *>(m_ptr.get());
|
||||
if (unsignedInt <= std::numeric_limits<int>::max()) {
|
||||
index = static_cast<int>(unsignedInt);
|
||||
} else {
|
||||
index = Id3Genres::genreCount();
|
||||
}
|
||||
} else {
|
||||
throw ConversionException("The assigned index/integer is of unappropriate size.");
|
||||
}
|
||||
index = static_cast<int>(*reinterpret_cast<std::int32_t *>(m_ptr.get()));
|
||||
break;
|
||||
default:
|
||||
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to genre index."));
|
||||
|
@ -470,6 +526,17 @@ PositionInSet TagValue::toPositionInSet() const
|
|||
default:
|
||||
throw ConversionException("The size of the assigned data is not appropriate.");
|
||||
}
|
||||
case TagDataType::UnsignedInteger:
|
||||
switch (m_size) {
|
||||
case sizeof(std::uint64_t): {
|
||||
const auto track = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
|
||||
if (track < std::numeric_limits<std::int32_t>::max()) {
|
||||
return PositionInSet(static_cast<std::int32_t>(track));
|
||||
}
|
||||
}
|
||||
default:;
|
||||
}
|
||||
throw ConversionException("The size of the assigned data is not appropriate.");
|
||||
default:
|
||||
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to position in set."));
|
||||
}
|
||||
|
@ -498,6 +565,17 @@ TimeSpan TagValue::toTimeSpan() const
|
|||
default:
|
||||
throw ConversionException("The size of the assigned integer is not appropriate for conversion to time span.");
|
||||
}
|
||||
case TagDataType::UnsignedInteger:
|
||||
switch (m_size) {
|
||||
case sizeof(std::uint64_t): {
|
||||
const auto ticks = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
|
||||
if (ticks < std::numeric_limits<std::int64_t>::max()) {
|
||||
return TimeSpan(static_cast<std::int64_t>(ticks));
|
||||
}
|
||||
}
|
||||
default:;
|
||||
}
|
||||
throw ConversionException("The size of the assigned data is not appropriate.");
|
||||
default:
|
||||
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to time span."));
|
||||
}
|
||||
|
@ -524,9 +602,10 @@ DateTime TagValue::toDateTime() const
|
|||
}
|
||||
case TagDataType::Integer:
|
||||
case TagDataType::DateTime:
|
||||
case TagDataType::UnsignedInteger:
|
||||
if (m_size == sizeof(std::int32_t)) {
|
||||
return DateTime(*(reinterpret_cast<std::uint32_t *>(m_ptr.get())));
|
||||
} else if (m_size == sizeof(std::int64_t)) {
|
||||
} else if (m_size == sizeof(std::uint64_t)) {
|
||||
return DateTime(*(reinterpret_cast<std::uint64_t *>(m_ptr.get())));
|
||||
} else {
|
||||
throw ConversionException("The size of the assigned integer is not appropriate for conversion to date time.");
|
||||
|
@ -568,6 +647,9 @@ Popularity TagValue::toPopularity() const
|
|||
}
|
||||
break;
|
||||
}
|
||||
case TagDataType::UnsignedInteger:
|
||||
popularity.rating = static_cast<double>(toUnsignedInteger());
|
||||
break;
|
||||
default:
|
||||
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to date time."));
|
||||
}
|
||||
|
@ -751,6 +833,9 @@ void TagValue::toString(string &result, TagTextEncoding encoding) const
|
|||
case TagDataType::Popularity:
|
||||
result = toPopularity().toString();
|
||||
break;
|
||||
case TagDataType::UnsignedInteger:
|
||||
result = numberToString(toUnsignedInteger());
|
||||
break;
|
||||
default:
|
||||
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
|
||||
}
|
||||
|
@ -838,6 +923,9 @@ void TagValue::toWString(std::u16string &result, TagTextEncoding encoding) const
|
|||
case TagDataType::Popularity:
|
||||
regularStrRes = toPopularity().toString();
|
||||
break;
|
||||
case TagDataType::UnsignedInteger:
|
||||
regularStrRes = numberToString(toUnsignedInteger());
|
||||
break;
|
||||
default:
|
||||
throw ConversionException(argsToString("Can not convert ", tagDataTypeString(m_type), " to string."));
|
||||
}
|
||||
|
@ -918,6 +1006,19 @@ void TagValue::assignInteger(int value)
|
|||
m_encoding = TagTextEncoding::Latin1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns the given unsigned integer \a value.
|
||||
* \param value Specifies the unsigned integer to be assigned.
|
||||
*/
|
||||
void TagValue::assignUnsignedInteger(std::uint64_t value)
|
||||
{
|
||||
m_size = sizeof(value);
|
||||
m_ptr = make_unique<char[]>(m_size);
|
||||
std::copy(reinterpret_cast<const char *>(&value), reinterpret_cast<const char *>(&value) + m_size, m_ptr.get());
|
||||
m_type = TagDataType::UnsignedInteger;
|
||||
m_encoding = TagTextEncoding::Latin1;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Assigns a copy of the given \a data.
|
||||
* \param data Specifies the data to be assigned.
|
||||
|
|
12
tagvalue.h
12
tagvalue.h
|
@ -102,6 +102,7 @@ enum class TagDataType : unsigned int {
|
|||
Binary, /**< unspecified binary data */
|
||||
Undefined, /**< undefined/invalid data type */
|
||||
Popularity, /**< rating with user info and play counter (as in ID3v2's "Popularimeter") */
|
||||
UnsignedInteger, /**< unsigned integer */
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -126,6 +127,7 @@ public:
|
|||
explicit TagValue(
|
||||
std::string_view text, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified);
|
||||
explicit TagValue(int value);
|
||||
explicit TagValue(std::uint64_t value);
|
||||
explicit TagValue(
|
||||
const char *data, std::size_t length, TagDataType type = TagDataType::Undefined, TagTextEncoding encoding = TagTextEncoding::Latin1);
|
||||
explicit TagValue(std::unique_ptr<char[]> &&data, std::size_t length, TagDataType type = TagDataType::Binary,
|
||||
|
@ -158,6 +160,7 @@ public:
|
|||
std::u16string toWString(TagTextEncoding encoding = TagTextEncoding::Unspecified) const;
|
||||
void toWString(std::u16string &result, TagTextEncoding encoding = TagTextEncoding::Unspecified) const;
|
||||
std::int32_t toInteger() const;
|
||||
std::uint64_t toUnsignedInteger() const;
|
||||
int toStandardGenreIndex() const;
|
||||
PositionInSet toPositionInSet() const;
|
||||
CppUtilities::TimeSpan toTimeSpan() const;
|
||||
|
@ -194,6 +197,7 @@ public:
|
|||
void assignText(
|
||||
std::string_view text, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified);
|
||||
void assignInteger(int value);
|
||||
void assignUnsignedInteger(std::uint64_t value);
|
||||
void assignStandardGenreIndex(int index);
|
||||
void assignData(const char *data, std::size_t length, TagDataType type = TagDataType::Binary, TagTextEncoding encoding = TagTextEncoding::Latin1);
|
||||
void assignData(std::unique_ptr<char[]> &&data, std::size_t length, TagDataType type = TagDataType::Binary,
|
||||
|
@ -318,6 +322,14 @@ inline TagValue::TagValue(int value)
|
|||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new TagValue holding the given unsigned integer \a value.
|
||||
*/
|
||||
inline TagParser::TagValue::TagValue(std::uint64_t value)
|
||||
: TagValue(reinterpret_cast<const char *>(&value), sizeof(value), TagDataType::UnsignedInteger)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new TagValue with a copy of the given \a data.
|
||||
*
|
||||
|
|
|
@ -24,6 +24,7 @@ class TagValueTests : public TestFixture {
|
|||
CPPUNIT_TEST(testBasics);
|
||||
CPPUNIT_TEST(testBinary);
|
||||
CPPUNIT_TEST(testInteger);
|
||||
CPPUNIT_TEST(testUnsignedInteger);
|
||||
CPPUNIT_TEST(testPositionInSet);
|
||||
CPPUNIT_TEST(testTimeSpan);
|
||||
CPPUNIT_TEST(testDateTime);
|
||||
|
@ -39,6 +40,7 @@ public:
|
|||
void testBasics();
|
||||
void testBinary();
|
||||
void testInteger();
|
||||
void testUnsignedInteger();
|
||||
void testPositionInSet();
|
||||
void testTimeSpan();
|
||||
void testDateTime();
|
||||
|
@ -77,10 +79,11 @@ void TagValueTests::testBinary()
|
|||
void TagValueTests::testInteger()
|
||||
{
|
||||
// positive number
|
||||
TagValue integer(42);
|
||||
auto integer = TagValue(42);
|
||||
CPPUNIT_ASSERT(!integer.isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL(TagDataType::Integer, integer.type());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<std::int32_t>(42), integer.toInteger());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(42), integer.toUnsignedInteger());
|
||||
CPPUNIT_ASSERT_EQUAL("42"s, integer.toString());
|
||||
integer.assignInteger(2);
|
||||
CPPUNIT_ASSERT_EQUAL("Country"s, string(Id3Genres::stringFromIndex(integer.toStandardGenreIndex())));
|
||||
|
@ -107,16 +110,41 @@ void TagValueTests::testInteger()
|
|||
CPPUNIT_ASSERT_MESSAGE("cleared vale considered empty", integer.isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("only date (but not type) cleared"s, TagDataType::Integer, integer.type());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<std::int32_t>(0), integer.toInteger());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(0), integer.toUnsignedInteger());
|
||||
CPPUNIT_ASSERT_EQUAL(string(), integer.toString());
|
||||
CPPUNIT_ASSERT_EQUAL(DateTime(), integer.toDateTime());
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan(), integer.toTimeSpan());
|
||||
}
|
||||
|
||||
void TagValueTests::testUnsignedInteger()
|
||||
{
|
||||
auto unsignedInteger = TagValue(static_cast<std::uint64_t>(42ul));
|
||||
CPPUNIT_ASSERT(!unsignedInteger.isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL(TagDataType::UnsignedInteger, unsignedInteger.type());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<std::int32_t>(42), unsignedInteger.toInteger());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(42), unsignedInteger.toUnsignedInteger());
|
||||
CPPUNIT_ASSERT_EQUAL("42"s, unsignedInteger.toString());
|
||||
unsignedInteger.assignUnsignedInteger(2);
|
||||
CPPUNIT_ASSERT_EQUAL("Country"s, string(Id3Genres::stringFromIndex(unsignedInteger.toStandardGenreIndex())));
|
||||
unsignedInteger.assignInteger(Id3Genres::emptyGenreIndex());
|
||||
CPPUNIT_ASSERT_EQUAL(Id3Genres::emptyGenreIndex(), unsignedInteger.toStandardGenreIndex());
|
||||
unsignedInteger.clearData();
|
||||
CPPUNIT_ASSERT_EQUAL(Id3Genres::emptyGenreIndex(), unsignedInteger.toStandardGenreIndex());
|
||||
|
||||
// zero
|
||||
unsignedInteger.assignInteger(0);
|
||||
CPPUNIT_ASSERT_MESSAGE("explicitly assigned zero not considered empty", !unsignedInteger.isEmpty());
|
||||
CPPUNIT_ASSERT_EQUAL("0"s, unsignedInteger.toString());
|
||||
CPPUNIT_ASSERT_EQUAL(DateTime(), unsignedInteger.toDateTime());
|
||||
CPPUNIT_ASSERT_EQUAL(TimeSpan(), unsignedInteger.toTimeSpan());
|
||||
}
|
||||
|
||||
void TagValueTests::testPositionInSet()
|
||||
{
|
||||
const TagValue test(PositionInSet(4, 23));
|
||||
CPPUNIT_ASSERT_EQUAL(PositionInSet(4, 23), test.toPositionInSet());
|
||||
CPPUNIT_ASSERT_EQUAL(test.toInteger(), 4);
|
||||
CPPUNIT_ASSERT_EQUAL(4, test.toInteger());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(4), test.toUnsignedInteger());
|
||||
CPPUNIT_ASSERT_EQUAL("4/23"s, test.toString());
|
||||
CPPUNIT_ASSERT_THROW(test.toStandardGenreIndex(), ConversionException);
|
||||
CPPUNIT_ASSERT_THROW(test.toDateTime(), ConversionException);
|
||||
|
@ -170,6 +198,7 @@ void TagValueTests::testString()
|
|||
CPPUNIT_ASSERT_EQUAL("\x31\0\x35\0"s, TagValue(15).toString(TagTextEncoding::Utf16LittleEndian));
|
||||
CPPUNIT_ASSERT_EQUAL("\0\x31\0\x35"s, TagValue(15).toString(TagTextEncoding::Utf16BigEndian));
|
||||
CPPUNIT_ASSERT_EQUAL(15, TagValue("\0\x31\0\x35"s, TagTextEncoding::Utf16BigEndian).toInteger());
|
||||
CPPUNIT_ASSERT_EQUAL(static_cast<std::uint64_t>(15), TagValue("\0\x31\0\x35"s, TagTextEncoding::Utf16BigEndian).toUnsignedInteger());
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(
|
||||
"original encoding preserved", "15ä"s, TagValue("15ä", 4, TagTextEncoding::Utf8).toString(TagTextEncoding::Unspecified));
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("original encoding preserved", "\0\x31\0\x35"s,
|
||||
|
|
Loading…
Reference in New Issue