#ifndef FIELDBASEDTAG_H #define FIELDBASEDTAG_H #include "./tag.h" #include #include namespace Media { /*! * \class Media::FieldMapBasedTagTraits * \brief Defines traits for the specified \a ImplementationType. * * A template specialization for each FieldMapBasedTag subclass must be provided. */ template class FieldMapBasedTagTraits {}; /*! * \class Media::FieldMapBasedTag * \brief The FieldMapBasedTag provides a generic implementation of Tag which stores * the tag fields using std::multimap. * * The FieldMapBasedTag class only provides the interface and common functionality. * It is meant to be subclassed using CRTP pattern. * * \remarks This template class is intended to be subclassed using * with the "Curiously recurring template pattern". */ template class FieldMapBasedTag : public Tag { friend class FieldMapBasedTagTraits; public: typedef typename FieldMapBasedTagTraits::implementationType implementationType; typedef typename FieldMapBasedTagTraits::fieldType fieldType; typedef typename FieldMapBasedTagTraits::fieldType::identifierType identifierType; typedef typename FieldMapBasedTagTraits::compare compare; FieldMapBasedTag(); TagType type() const; const char *typeName() const; TagTextEncoding proposedTextEncoding() const; const TagValue &value(const identifierType &id) const; const TagValue &value(KnownField field) const; std::vector values(const identifierType &id) const; std::vector values(KnownField field) const; bool setValue(const identifierType &id, const TagValue &value); bool setValue(KnownField field, const TagValue &value); bool setValues(const identifierType &id, const std::vector &values); bool setValues(KnownField field, const std::vector &values); bool hasField(KnownField field) const; bool hasField(const identifierType &id) const; void removeAllFields(); const std::multimap &fields() const; std::multimap &fields(); unsigned int fieldCount() const; identifierType fieldId(KnownField value) const; KnownField knownField(const identifierType &id) const; bool supportsField(KnownField field) const; using Tag::proposedDataType; TagDataType proposedDataType(const identifierType &id) const; int insertFields(const FieldMapBasedTag &from, bool overwrite); unsigned int insertValues(const Tag &from, bool overwrite); void ensureTextValuesAreProperlyEncoded(); protected: const TagValue &internallyGetValue(const identifierType &id) const; bool internallySetValue(const identifierType &id, const TagValue &value); bool internallyHasField(const identifierType &id) const; // no default implementation: identifierType internallyGetFieldId(KnownField field) const; // no default implementation: KnownField internallyGetKnownField(const identifierType &id) const; TagDataType internallyGetProposedDataType(const identifierType &id) const; private: std::multimap m_fields; }; /*! * \fn FieldMapBasedTag::fieldId() * \brief Returns the ID for the specified \a field. * * Needs to be implemented when subclassing. */ /*! * \fn FieldMapBasedTag::knownField() * \brief Returns the field for the specified \a ID. * * Needs to be implemented when subclassing. */ /*! * \brief Constructs a new FieldMapBasedTag. */ template FieldMapBasedTag::FieldMapBasedTag() {} template TagType FieldMapBasedTag::type() const { return ImplementationType::tagType; } template const char *FieldMapBasedTag::typeName() const { return ImplementationType::tagName; } template TagTextEncoding FieldMapBasedTag::proposedTextEncoding() const { return ImplementationType::defaultTextEncoding; } /*! * \brief Default implementation for value(). * \remarks Shadow in subclass to provide custom implementation. */ template const TagValue &FieldMapBasedTag::internallyGetValue(const identifierType &id) const { auto i = m_fields.find(id); return i != m_fields.end() ? i->second.value() : TagValue::empty(); } /*! * \brief Returns the value of the field with the specified \a id. * \sa Tag::value() */ template inline const TagValue &FieldMapBasedTag::value(const identifierType &id) const { return static_cast(this)->internallyGetValue(id); } template inline const TagValue &FieldMapBasedTag::value(KnownField field) const { return value(fieldId(field)); } /*! * \brief Returns the values of the field with the specified \a id. * \sa Tag::values() */ template inline std::vector FieldMapBasedTag::values(const identifierType &id) const { auto range = m_fields.equal_range(id); std::vector values; for(auto i = range.first; i != range.second; ++i) { if(!i->second.value().isEmpty()) { values.push_back(&i->second.value()); } } return values; } template inline std::vector FieldMapBasedTag::values(KnownField field) const { return values(fieldId(field)); } template inline bool FieldMapBasedTag::setValue(KnownField field, const TagValue &value) { return setValue(fieldId(field), value); } /*! * \brief Default implementation for setValue(). * \remarks Shadow in subclass to provide custom implementation. */ template bool FieldMapBasedTag::internallySetValue(const identifierType &id, const TagValue &value) { auto i = m_fields.find(id); if(i != m_fields.end()) { // field already exists -> set its value i->second.setValue(value); } else if(!value.isEmpty()) { // field doesn't exist -> create new one if value is not null m_fields.insert(std::make_pair(id, fieldType(id, value))); } else { // otherwise return false return false; } return true; } /*! * \brief Assigns the given \a value to the field with the specified \a id. * \sa Tag::setValue() */ template bool FieldMapBasedTag::setValue(const identifierType &id, const Media::TagValue &value) { return static_cast(this)->internallySetValue(id, value); } /*! * \brief Assigns the given \a values to the field with the specified \a id. * \remarks There might me more than one value assigned to an \a id. Whereas setValue() only alters the first value, this * method will replace all currently assigned values with the specified \a values. * \sa Tag::setValues() */ template bool FieldMapBasedTag::setValues(const identifierType &id, const std::vector &values) { auto valuesIterator = values.cbegin(); auto range = m_fields.equal_range(id); // iterate through all specified and all existing values for(; valuesIterator != values.cend() && range.first != range.second; ++valuesIterator) { // replace existing value with non-empty specified value if(!valuesIterator->isEmpty()) { range.first->second.setValue(*valuesIterator); ++range.first; } } // add remaining specified values (there are more specified values than existing ones) for(; valuesIterator != values.cend(); ++valuesIterator) { m_fields.insert(std::make_pair(id, fieldType(id, *valuesIterator))); } // remove remaining existing values (there are more existing values than specified ones) for(; range.first != range.second; ++range.first) { range.first->second.setValue(TagValue()); } return true; } /*! * \brief Assigns the given \a values to the field with the specified \a id. * \remarks There might me more than one value assigned to a \a field. Whereas setValue() only alters the first value, this * method will replace all currently assigned values with the specified \a values. * \sa Tag::setValues() */ template bool FieldMapBasedTag::setValues(KnownField field, const std::vector &values) { return setValues(fieldId(field), values); } template inline bool FieldMapBasedTag::hasField(KnownField field) const { return hasField(fieldId(field)); } /*! * \brief Default implementation for hasField(). * \remarks Shadow in subclass to provide custom implementation. */ template bool FieldMapBasedTag::internallyHasField(const identifierType &id) const { for (auto range = m_fields.equal_range(id); range.first != range.second; ++range.first) { if(!range.first->second.value().isEmpty()) { return true; } } return false; } /*! * \brief Returns an indication whether the field with the specified \a id is present. */ template inline bool FieldMapBasedTag::hasField(const identifierType &id) const { return static_cast(this)->internallyHasField(id); } template inline void FieldMapBasedTag::removeAllFields() { m_fields.clear(); } /*! * \brief Returns the fields of the tag by providing direct access to the field map of the tag. */ template inline auto FieldMapBasedTag::fields() const -> const std::multimap & { return m_fields; } /*! * \brief Returns the fields of the tag by providing direct access to the field map of the tag. */ template inline auto FieldMapBasedTag::fields() -> std::multimap & { return m_fields; } template unsigned int FieldMapBasedTag::fieldCount() const { unsigned int count = 0; for(const auto &field : m_fields) { if(!field.second.value().isEmpty()) { ++count; } } return count; } /*! * \brief Returns the field ID for the specified \a value. * \remarks Must be implemented in internallyGetFieldId() when creating subclass. */ template inline typename FieldMapBasedTag::identifierType FieldMapBasedTag::fieldId(KnownField value) const { return static_cast(this)->internallyGetFieldId(value); } /*! * \brief Returns the KnownField for the specified \a id. * \remarks Must be implemented in internallyGetKnownField() when creating subclass. */ template inline KnownField FieldMapBasedTag::knownField(const identifierType &id) const { return static_cast *>(this)->internallyGetKnownField(id); } template inline bool FieldMapBasedTag::supportsField(KnownField field) const { static identifierType def; return fieldId(field) != def; } /*! * \brief Default implementation for proposedDataType(). * \remarks Shadow in subclass to provide custom implementation. */ template inline TagDataType FieldMapBasedTag::internallyGetProposedDataType(const identifierType &id) const { return Tag::proposedDataType(knownField(id)); } /*! * \brief Returns the proposed data type for the field with the specified \a id. */ template inline TagDataType FieldMapBasedTag::proposedDataType(const identifierType &id) const { return static_cast(this)->determineProposedDataType(id); } /*! * \brief Inserts all fields \a from another tag of the same field type and compare function. * \param from Specifies the tag the fields should be inserted from. * \param overwrite Indicates whether existing fields should be overwritten. * \return Returns the number of fields that have been inserted. */ template int FieldMapBasedTag::insertFields(const FieldMapBasedTag &from, bool overwrite) { int fieldsInserted = 0; for(const auto &pair : from.fields()) { const fieldType &fromField = pair.second; if(fromField.value().isEmpty()) continue; bool fieldInserted = false; auto range = fields().equal_range(fromField.id()); for(auto i = range.first; i != range.second; ++i) { fieldType &ownField = i->second; if((fromField.isTypeInfoAssigned() && ownField.isTypeInfoAssigned() && fromField.typeInfo() == ownField.typeInfo()) || (!fromField.isTypeInfoAssigned() && ! ownField.isTypeInfoAssigned())) { if(overwrite || ownField.value().isEmpty()) { ownField = fromField; ++fieldsInserted; } fieldInserted = true; continue; } } if(!fieldInserted) { fields().insert(std::make_pair(fromField.id(), fromField)); ++fieldsInserted; } } return fieldsInserted; } template unsigned int FieldMapBasedTag::insertValues(const Tag &from, bool overwrite) { if(type() == from.type()) { // the tags are of the same type, we can insert the fields directly return insertFields(static_cast &>(from), overwrite); } else { return Tag::insertValues(from, overwrite); } } template void FieldMapBasedTag::ensureTextValuesAreProperlyEncoded() { for(auto &field : fields()) { field.second.value().convertDataEncodingForTag(this); } } } #endif // FIELDBASEDTAG_H