#ifndef TAG_PARSER_TAGVALUE_H #define TAG_PARSER_TAGVALUE_H #include "./positioninset.h" #include #include #include #include #include #include #include #include namespace TagParser { class Tag; class Id3v2Frame; /*! * \brief Specifies the text encoding. */ enum class TagTextEncoding : unsigned int { Latin1, /**< ISO/IEC 8859-1 aka "Latin 1" */ Utf8, /**< UTF-8 */ Utf16LittleEndian, /**< UTF-16 (little endian) */ Utf16BigEndian, /**< UTF-16 (big endian) */ Unspecified /**< unspecified encoding */ }; /*! * \brief Returns the size of one character for the specified \a encoding in bytes. * \remarks For variable-width encoding the minimum size is returned. */ constexpr int characterSize(TagTextEncoding encoding) { switch (encoding) { case TagTextEncoding::Latin1: case TagTextEncoding::Utf8: return 1; case TagTextEncoding::Utf16LittleEndian: case TagTextEncoding::Utf16BigEndian: return 2; default: return 0; } } /*! * \brief Specifies the data type. */ enum class TagDataType : unsigned int { Text, /**< text/string */ Integer, /**< integer */ PositionInSet, /**< position in set, see TagParser::PositionInSet */ StandardGenreIndex, /**< pre-defined genre name denoted by numerical code */ TimeSpan, /**< time span, see ChronoUtils::TimeSpan */ DateTime, /**< date time, see ChronoUtils::DateTime */ Picture, /**< picture file */ Binary, /**< unspecified binary data */ Undefined /**< undefined/invalid data type */ }; class TAG_PARSER_EXPORT TagValue { public: // constructor, destructor TagValue(); TagValue(const char *text, std::size_t textSize, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified); TagValue(const char *text, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified); TagValue( const std::string &text, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified); TagValue(int value); TagValue(const char *data, std::size_t length, TagDataType type = TagDataType::Undefined, TagTextEncoding encoding = TagTextEncoding::Latin1); TagValue(std::unique_ptr &&data, std::size_t length, TagDataType type = TagDataType::Binary, TagTextEncoding encoding = TagTextEncoding::Latin1); TagValue(PositionInSet value); TagValue(const TagValue &other); TagValue(TagValue &&other) = default; ~TagValue(); // operators TagValue &operator=(const TagValue &other); TagValue &operator=(TagValue &&other) = default; bool operator==(const TagValue &other) const; bool operator!=(const TagValue &other) const; // methods bool isEmpty() const; void clearData(); void clearMetadata(); void clearDataAndMetadata(); TagDataType type() const; std::string toString(TagTextEncoding encoding = TagTextEncoding::Unspecified) const; void toString(std::string &result, TagTextEncoding encoding = TagTextEncoding::Unspecified) const; std::u16string toWString(TagTextEncoding encoding = TagTextEncoding::Unspecified) const; void toWString(std::u16string &result, TagTextEncoding encoding = TagTextEncoding::Unspecified) const; int32 toInteger() const; int toStandardGenreIndex() const; PositionInSet toPositionInSet() const; ChronoUtilities::TimeSpan toTimeSpan() const; ChronoUtilities::DateTime toDateTime() const; std::size_t dataSize() const; char *dataPointer(); const char *dataPointer() const; const std::string &description() const; void setDescription(const std::string &value, TagTextEncoding encoding = TagTextEncoding::Latin1); const std::string &mimeType() const; void setMimeType(const std::string &mimeType); const std::string &language() const; void setLanguage(const std::string &language); bool isLabeledAsReadonly() const; void setReadonly(bool readOnly); TagTextEncoding dataEncoding() const; void convertDataEncoding(TagTextEncoding encoding); void convertDataEncodingForTag(const Tag *tag); TagTextEncoding descriptionEncoding() const; static const TagValue &empty(); void assignText(const char *text, std::size_t textSize, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified); void assignText( const std::string &text, TagTextEncoding textEncoding = TagTextEncoding::Latin1, TagTextEncoding convertTo = TagTextEncoding::Unspecified); void assignInteger(int 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 &&data, std::size_t length, TagDataType type = TagDataType::Binary, TagTextEncoding encoding = TagTextEncoding::Latin1); void assignPosition(PositionInSet value); void assignTimeSpan(ChronoUtilities::TimeSpan value); void assignDateTime(ChronoUtilities::DateTime value); static void stripBom(const char *&text, std::size_t &length, TagTextEncoding encoding); static void ensureHostByteOrder(std::u16string &u16str, TagTextEncoding currentEncoding); template , std::is_same::type>::type, const TagValue>> * = nullptr> static std::vector toStrings(const ContainerType &values, TagTextEncoding encoding = TagTextEncoding::Utf8); private: std::unique_ptr m_ptr; std::size_t m_size; std::string m_desc; std::string m_mimeType; std::string m_language; TagDataType m_type; TagTextEncoding m_encoding; TagTextEncoding m_descEncoding; bool m_labeledAsReadonly; }; /*! * \brief Constructs an empty TagValue. */ inline TagValue::TagValue() : m_size(0) , m_type(TagDataType::Undefined) , m_encoding(TagTextEncoding::Latin1) , m_descEncoding(TagTextEncoding::Latin1) , m_labeledAsReadonly(false) { } /*! * \brief Destroys the TagValue. */ inline TagValue::~TagValue() { } /*! * \brief Constructs a new TagValue holding a copy of the given \a text. * \param text Specifies the text to be assigned. * \param textSize Specifies the size of \a text. (The actual number of bytes, not the number of characters.) * \param textEncoding Specifies the encoding of the given \a text. * \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to * use \a textEncoding without any character set conversions. * \throws Throws a ConversionException if the conversion the specified character set fails. * \remarks Strips the BOM of the specified \a text. */ inline TagValue::TagValue(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo) : m_descEncoding(TagTextEncoding::Latin1) , m_labeledAsReadonly(false) { assignText(text, textSize, textEncoding, convertTo); } /*! * \brief Constructs a new TagValue holding a copy of the given \a text. * \param text Specifies the text to be assigned. This string must be null-terminated. * \param textEncoding Specifies the encoding of the given \a text. * \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to * use \a textEncoding without any character set conversions. * \throws Throws a ConversionException if the conversion the specified character set fails. * \remarks Strips the BOM of the specified \a text. */ inline TagValue::TagValue(const char *text, TagTextEncoding textEncoding, TagTextEncoding convertTo) { assignText(text, std::strlen(text), textEncoding, convertTo); } /*! * \brief Constructs a new TagValue holding a copy of the given \a text. * \param text Specifies the text to be assigned. * \param textEncoding Specifies the encoding of the given \a text. * \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to * use \a textEncoding without any character set conversions. * \throws Throws a ConversionException if the conversion the specified character set fails. * \remarks Strips the BOM of the specified \a text. */ inline TagValue::TagValue(const std::string &text, TagTextEncoding textEncoding, TagTextEncoding convertTo) : m_descEncoding(TagTextEncoding::Latin1) , m_labeledAsReadonly(false) { assignText(text, textEncoding, convertTo); } /*! * \brief Constructs a new TagValue holding the given integer \a value. */ inline TagValue::TagValue(int value) : TagValue(reinterpret_cast(&value), sizeof(value), TagDataType::Integer) { } /*! * \brief Constructs a new TagValue with a copy of the given \a data. * * \param data Specifies a pointer to the data. * \param length Specifies the length of the data. * \param type Specifies the type of the data as TagDataType. * \param encoding Specifies the encoding of the data as TagTextEncoding. The * encoding will only be considered if a text is assigned. * \remarks Strips the BOM of the specified \a data if \a type is TagDataType::Text. */ inline TagValue::TagValue(const char *data, std::size_t length, TagDataType type, TagTextEncoding encoding) : m_size(length) , m_type(type) , m_encoding(encoding) , m_descEncoding(TagTextEncoding::Latin1) , m_labeledAsReadonly(false) { if (length) { if (type == TagDataType::Text) { stripBom(data, m_size, encoding); } m_ptr = std::make_unique(m_size); std::copy(data, data + m_size, m_ptr.get()); } } /*! * \brief Constructs a new TagValue holding with the given \a data. * * The \a data is not copied. It is moved. * * \param data Specifies a pointer to the data. * \param length Specifies the length of the data. * \param type Specifies the type of the data as TagDataType. * \param encoding Specifies the encoding of the data as TagTextEncoding. The * encoding will only be considered if a text is assigned. * \remarks Does not strip the BOM so for consistency the caller must ensure there is no BOM present. */ inline TagValue::TagValue(std::unique_ptr &&data, std::size_t length, TagDataType type, TagTextEncoding encoding) : m_size(length) , m_type(type) , m_encoding(encoding) , m_descEncoding(TagTextEncoding::Latin1) , m_labeledAsReadonly(false) { if (length) { m_ptr = move(data); } } /*! * \brief Constructs a new TagValue holding a copy of the given PositionInSet \a value. * \param value Specifies the PositionInSet. */ inline TagValue::TagValue(PositionInSet value) : TagValue(reinterpret_cast(&value), sizeof(value), TagDataType::PositionInSet) { } /*! * \brief Returns whether both instances are not equal. * \remarks Simply the negation of operator==() so check there for details. */ inline bool TagValue::operator!=(const TagValue &other) const { return !(*this == other); } /*! * \brief Assigns a copy of the given \a text. * \param text Specifies the text to be assigned. * \param textEncoding Specifies the encoding of the given \a text. * \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to * use \a textEncoding without any character set conversions. * \throws Throws a ConversionException if the conversion the specified character set fails. * \remarks Strips the BOM of the specified \a text. */ inline void TagValue::assignText(const std::string &text, TagTextEncoding textEncoding, TagTextEncoding convertTo) { assignText(text.data(), text.size(), textEncoding, convertTo); } /*! * \brief Assigns the given PositionInSet \a value. */ inline void TagValue::assignPosition(PositionInSet value) { if (value.isNull()) { m_type = TagDataType::PositionInSet; clearData(); } else { assignData(reinterpret_cast(&value), sizeof(value), TagDataType::PositionInSet); } } /*! * \brief Assigns the given TimeSpan \a value. */ inline void TagValue::assignTimeSpan(ChronoUtilities::TimeSpan value) { assignData(reinterpret_cast(&value), sizeof(value), TagDataType::TimeSpan); } /*! * \brief Assigns the given DateTime \a value. */ inline void TagValue::assignDateTime(ChronoUtilities::DateTime value) { assignData(reinterpret_cast(&value), sizeof(value), TagDataType::DateTime); } /*! * \brief Assigns the given standard genre \a index to be assigned. * \param index Specifies the index to be assigned. * \sa List of genres - Wikipedia */ inline void TagValue::assignStandardGenreIndex(int index) { assignInteger(index); m_type = TagDataType::StandardGenreIndex; } /*! * \brief Returns the type of the assigned value. */ inline TagDataType TagValue::type() const { return m_type; } /*! * \brief Converts the value of the current TagValue object to its equivalent * std::string representation. * \param encoding Specifies the encoding to to be used; set to TagTextEncoding::Unspecified to use the * present encoding without any character set conversion. * \throws Throws ConversionException on failure. */ inline std::string TagValue::toString(TagTextEncoding encoding) const { std::string res; toString(res, encoding); return res; } /*! * \brief Converts the value of the current TagValue object to its equivalent * std::wstring representation. * \throws Throws ConversionException on failure. * \remarks Use this only, if \a encoding is an UTF-16 encoding. */ inline std::u16string TagValue::toWString(TagTextEncoding encoding) const { std::u16string res; toWString(res, encoding); return res; } /*! * \brief Returns an indication whether an value is assigned. * \remarks Meta data such as description and MIME type is not considered as an assigned value. */ inline bool TagValue::isEmpty() const { return m_ptr == nullptr || m_size == 0; } /*! * \brief Clears the assigned data. * \remarks Meta data such as description and MIME type remains unaffected. * \sa clearMetadata() * \sa clearDataAndMetadata() */ inline void TagValue::clearData() { m_size = 0; m_ptr.reset(); } /*! * \brief Wipes assigned data including meta data. * \sa clearData() * \sa clearMetadata() */ inline void TagValue::clearDataAndMetadata() { clearData(); clearMetadata(); } /*! * \brief Returns the size of the assigned value in bytes. * \remarks Meta data such as description and MIME type is not considered as part of the assigned value. */ inline std::size_t TagValue::dataSize() const { return m_size; } /*! * \brief Returns a pointer to the raw data assigned to the current instance. * \remarks The instance keeps ownership over the data which will be invalidated when the * TagValue gets destroyed or another value is assigned. * \remarks The raw data is not null terminated. See dataSize(). */ inline char *TagValue::dataPointer() { return m_ptr.get(); } inline const char *TagValue::dataPointer() const { return m_ptr.get(); } /*! * \brief Returns the description. * \remarks The usage of this meta information depends on the tag implementation. * \sa descriptionEncoding() * \sa setDescription() */ inline const std::string &TagValue::description() const { return m_desc; } /*! * \brief Sets the description. * \param value Specifies the description. * \param encoding Specifies the encoding used to provide the description. * \remarks The usage of this meta information depends on the tag implementation. * \sa description() * \sa descriptionEncoding() */ inline void TagValue::setDescription(const std::string &value, TagTextEncoding encoding) { m_desc = value; m_descEncoding = encoding; } /*! * \brief Returns the MIME type. * \remarks The usage of this meta information depends on the tag implementation. * \sa setMimeType() */ inline const std::string &TagValue::mimeType() const { return m_mimeType; } /*! * \brief Sets the MIME type. * \remarks The usage of this meta information depends on the tag implementation. * \sa mimeType() */ inline void TagValue::setMimeType(const std::string &mimeType) { m_mimeType = mimeType; } /*! * \brief Returns the language. * \remarks The usage of this meta information depends on the tag implementation. * \sa setLanguage() */ inline const std::string &TagValue::language() const { return m_language; } /*! * \brief Sets the language. * \remarks The usage of this meta information depends on the tag implementation. * \sa language() */ inline void TagValue::setLanguage(const std::string &language) { m_language = language; } /*! * \brief Returns an indication whether the value is labeled as read-only. * \remarks The usage of this meta information depends on the tag implementation. * \remarks This is just an additional information. It has no effect on the behavior * of the TagValue thus assignments can still be performed (to prohibit * assignments simply use the "const" keyword). * \sa setReadonly() */ inline bool TagValue::isLabeledAsReadonly() const { return m_labeledAsReadonly; } /*! * \brief Sets whether the TagValue is labeled as read-only. * \remarks The usage of this meta information depends on the tag implementation. * \remarks This is just an additional information. It has no effect on the behavior * of the TagValue thus assignments can still be performed (to prohibit * assignments simply use the "const" keyword). * \sa isLabeledAsReadonly() */ inline void TagValue::setReadonly(bool readOnly) { m_labeledAsReadonly = readOnly; } /*! * \brief Returns the data encoding. * \remarks This value is only relevant if type() equals TagDataType::Text. * \sa assignText() */ inline TagTextEncoding TagValue::dataEncoding() const { return m_encoding; } /*! * \brief Returns the description encoding. * \remarks This value is only relevant if a description is assigned. * \sa description(), setDescription() */ inline TagTextEncoding TagValue::descriptionEncoding() const { return m_descEncoding; } /*! * \brief Converts the specified \a values to string using the specified \a encoding. * \throws Throws ConversionException on failure. * \sa toString() */ template , std::is_same::type>::type, const TagValue>> *> std::vector TagValue::toStrings(const ContainerType &values, TagTextEncoding encoding) { std::vector res; res.reserve(values.size()); for (const auto &value : values) { res.emplace_back(Traits::dereferenceMaybe(value).toString(encoding)); } return res; } } // namespace TagParser #endif // TAG_PARSER_TAGVALUE_H