#ifndef TAG_PARSER_FIELDBASEDTAG_H #define TAG_PARSER_FIELDBASEDTAG_H #include "./tag.h" #include #include namespace TagParser { /*! * \class TagParser::FieldMapBasedTagTraits * \brief Defines traits for the specified \a ImplementationType. * * A template specialization for each FieldMapBasedTag subclass must be provided. */ template class FieldMapBasedTagTraits {}; /*! * \class TagParser::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: using FieldType = typename FieldMapBasedTagTraits::FieldType; using IdentifierType = typename FieldMapBasedTagTraits::FieldType::IdentifierType; using Compare = typename FieldMapBasedTagTraits::Compare; FieldMapBasedTag(); TagType type() const; std::string_view 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(); std::size_t 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; std::size_t insertFields(const FieldMapBasedTag &from, bool overwrite); std::size_t insertValues(const Tag &from, bool overwrite); void ensureTextValuesAreProperlyEncoded(); protected: using CRTPBase = FieldMapBasedTag; const TagValue &internallyGetValue(const IdentifierType &id) const; void internallyGetValuesFromField(const FieldType &field, std::vector &values) const; std::vector internallyGetValues(const IdentifierType &id) const; bool internallySetValue(const IdentifierType &id, const TagValue &value); bool internallySetValues(const IdentifierType &id, const std::vector &values); 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 std::string_view 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 Default way to gather values from a field in internallyGetValues(). * \remarks Shadow in subclass to provide custom implementation. */ template void FieldMapBasedTag::internallyGetValuesFromField( const FieldMapBasedTag::FieldType &field, std::vector &values) const { if (!field.value().isEmpty()) { values.emplace_back(&field.value()); } } /*! * \brief Default implementation for values(). * \remarks Shadow in subclass to provide custom implementation. */ template std::vector FieldMapBasedTag::internallyGetValues(const IdentifierType &id) const { auto range = m_fields.equal_range(id); std::vector values; for (auto i = range.first; i != range.second; ++i) { static_cast(this)->internallyGetValuesFromField(i->second, values); } return values; } /*! * \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 { return static_cast(this)->internallyGetValues(id); } template inline std::vector FieldMapBasedTag::values(KnownField field) const { return static_cast(this)->values(fieldId(field)); } template inline bool FieldMapBasedTag::setValue(KnownField field, const TagValue &value) { const auto id = fieldId(field); if constexpr (std::is_arithmetic_v) { if (!id) { return false; } } else { if (id.empty()) { return false; } } return setValue(id, 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 Default implementation for setValues(). * \remarks Shadow in subclass to provide custom implementation. */ template bool FieldMapBasedTag::internallySetValues(const FieldMapBasedTag::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()) { auto &field = range.first->second; field.clearValue(); field.setValue(*valuesIterator); ++range.first; } } // add remaining specified values (there are more specified values than existing ones) for (; valuesIterator != values.cend(); ++valuesIterator) { if (!valuesIterator->isEmpty()) { 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.clearValue(); } 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 TagParser::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) { return static_cast(this)->internallySetValues(id, values); } /*! * \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) { const auto id = fieldId(field); if constexpr (std::is_arithmetic_v) { if (!id) { return false; } } else { if (id.empty()) { return false; } } return setValues(id, 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 std::size_t FieldMapBasedTag::fieldCount() const { auto count = std::size_t(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 const auto def = IdentifierType(); 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 std::size_t FieldMapBasedTag::insertFields(const FieldMapBasedTag &from, bool overwrite) { auto fieldsInserted = std::size_t(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 std::size_t 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); } } } // namespace TagParser #endif // TAG_PARSER_FIELDBASEDTAG_H