From e4d955d6ef703be049f47df278b30e567ab27924 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 21 Jan 2012 14:52:24 +0100 Subject: [PATCH] Migration to new PropertyMap ... done ape to mod. --- taglib/ape/apefile.cpp | 32 ++++++---- taglib/ape/apefile.h | 16 +++-- taglib/ape/apetag.cpp | 87 ++++++++++++++----------- taglib/ape/apetag.h | 17 +++-- taglib/flac/flacfile.cpp | 44 ++++++++----- taglib/flac/flacfile.h | 12 ++-- taglib/it/itfile.cpp | 8 +-- taglib/it/itfile.h | 10 +-- taglib/mod/modfile.cpp | 8 +-- taglib/mod/modfile.h | 12 ++-- taglib/mod/modtag.cpp | 50 ++++++++++----- taglib/mod/modtag.h | 15 +++-- taglib/tag.cpp | 110 ++++++++++++++++++++------------ taglib/tag.h | 40 ++++++------ taglib/toolkit/tfile.cpp | 103 ++++++++++++++++++++---------- taglib/toolkit/tfile.h | 25 ++++++-- taglib/toolkit/tpropertymap.cpp | 27 +++++--- taglib/toolkit/tpropertymap.h | 16 ++++- 18 files changed, 398 insertions(+), 234 deletions(-) diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index 7c63412e..6e806415 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -109,23 +109,31 @@ TagLib::Tag *APE::File::tag() const return &d->tag; } -TagLib::TagDict APE::File::toDict(void) const +PropertyMap APE::File::properties() const { - if (d->hasAPE) - return d->tag.access(APEIndex, false)->toDict(); - if (d->hasID3v1) - return d->tag.access(ID3v1Index, false)->toDict(); - return TagLib::TagDict(); + if(d->hasAPE) + return d->tag.access(APEIndex, false)->properties(); + if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->properties(); + return PropertyMap(); } -void APE::File::fromDict(const TagDict &dict) +void APE::File::removeUnsupportedProperties(const StringList &properties) { - if (d->hasAPE) - d->tag.access(APEIndex, false)->fromDict(dict); - else if (d->hasID3v1) - d->tag.access(ID3v1Index, false)->fromDict(dict); + if(d->hasAPE) + d->tag.access(APEIndex, false)->removeUnsupportedProperties(properties); + if(d->hasID3v1) + d->tag.access(ID3v1Index, false)->removeUnsupportedProperties(properties); +} + +PropertyMap APE::File::setProperties(const PropertyMap &properties) +{ + if(d->hasAPE) + return d->tag.access(APEIndex, false)->setProperties(properties); + else if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->setProperties(properties); else - d->tag.access(APE, true)->fromDict(dict); + return d->tag.access(APE, true)->setProperties(properties); } APE::Properties *APE::File::audioProperties() const diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index ab290b83..0bdbd422 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -111,18 +111,24 @@ namespace TagLib { virtual TagLib::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. + * Implements the unified property interface -- export function. * If the file contains both an APE and an ID3v1 tag, only APE - * will be converted to the TagDict. + * will be converted to the PropertyMap. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. + * Removes unsupported properties. Forwards to the actual Tag's + * removeUnsupportedProperties() function. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. * As for the export, only one tag is taken into account. If the file * has no tag at all, APE will be created. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the APE::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index e1bf40c2..5393d72d 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -174,61 +174,73 @@ void APE::Tag::setTrack(uint i) addValue("TRACK", String::number(i), true); } -TagDict APE::Tag::toDict() const +// conversions of tag keys between what we use in PropertyMap and what's usual +// for APE tags +static const uint keyConversionsSize = 5; //usual, APE +static const char *keyConversions[][2] = {{"TRACKNUMBER", "TRACK" }, + {"DATE", "YEAR" }, + {"ALBUMARTIST", "ALBUM ARTIST"}, + {"DISCNUMBER", "DISC" }, + {"REMIXER", "MIXARTIST" }}; + +PropertyMap APE::Tag::properties() const { - TagDict dict; + PropertyMap properties; ItemListMap::ConstIterator it = itemListMap().begin(); - for (; it != itemListMap().end(); ++it) { - String tagName = it->first.upper(); - // These two tags need to be handled specially; in APE tags the track number is usually - // named TRACK instead of TRACKNUMBER, the date tag is YEAR instead of DATE - // - if (tagName == "TRACK") - tagName = "TRACKNUMBER"; - else if (tagName == "YEAR") - tagName = "DATE"; - else if (tagName == "ALBUM ARTIST") - tagName = "ALBUMARTIST"; - if (it->second.type() == Item::Text) - dict[tagName].append(it->second.toStringList()); + for(; it != itemListMap().end(); ++it) { + String tagName = PropertyMap::prepareKey(it->first); + // if the item is Binary or Locator, or if the key is an invalid string, + // add to unsupportedData + if(it->second.type() != Item::Text || tagName.isNull()) + properties.unsupportedData().append(it->first); + else { + // Some tags need to be handled specially + for(uint i = 0; i < keyConversionsSize; ++i) + if(tagName == keyConversions[i][1]) + tagName = keyConversions[i][0]; + properties[tagName].append(it->second.toStringList()); + } } - return dict; + return properties; } -void APE::Tag::fromDict(const TagDict &origDict) +void APE::Tag::removeUnsupportedProperties(const StringList &properties) { - TagDict dict(origDict); // make a local copy that can be modified + StringList::ConstIterator it = properties.begin(); + for(; it != properties.end(); ++it) + removeItem(*it); +} - // see comment in toDict() about TRACKNUMBER and YEAR - if (dict.contains("TRACKNUMBER")) { - dict.insert("TRACK", dict["TRACKNUMBER"]); - dict.erase("TRACKNUMBER"); - } - if (dict.contains("DATE")) { - dict.insert("YEAR", dict["DATE"]); - dict.erase("DATE"); - } +PropertyMap APE::Tag::setProperties(const PropertyMap &origProps) +{ + PropertyMap properties(origProps); // make a local copy that can be modified + + // see comment in properties() + for(uint i = 0; i < keyConversionsSize; ++i) + if(properties.contains(keyConversions[i][0])) { + properties.insert(keyConversions[i][1], properties[keyConversions[i][0]]); + properties.erase(keyConversions[i][0]); + } // first check if tags need to be removed completely StringList toRemove; ItemListMap::ConstIterator remIt = itemListMap().begin(); - for (; remIt != itemListMap().end(); ++remIt) { - if (remIt->second.type() != APE::Item::Text) - // ignore binary and locator APE items - continue; - if (!dict.contains(remIt->first.upper())) + for(; remIt != itemListMap().end(); ++remIt) { + String key = PropertyMap::prepareKey(remIt->first); + // only remove if a) key is valid, b) type is text, c) key not contained in new properties + if(!key.isNull() && remIt->second.type() == APE::Item::Text && !properties.contains(key)) toRemove.append(remIt->first); } for (StringList::Iterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++) removeItem(*removeIt); - // now sync in the "forward direction - TagDict::ConstIterator it = dict.begin(); - for (; it != dict.end(); ++it) { + // now sync in the "forward direction" + PropertyMap::ConstIterator it = properties.begin(); + for(; it != properties.end(); ++it) { const String &tagName = it->first; - if (!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) { - if (it->second.size() == 0) + if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) { + if(it->second.size() == 0) removeItem(tagName); else { StringList::ConstIterator valueIt = it->second.begin(); @@ -239,6 +251,7 @@ void APE::Tag::fromDict(const TagDict &origDict) } } } + return PropertyMap; } APE::Footer *APE::Tag::footer() const diff --git a/taglib/ape/apetag.h b/taglib/ape/apetag.h index 089420ea..8520609e 100644 --- a/taglib/ape/apetag.h +++ b/taglib/ape/apetag.h @@ -107,20 +107,25 @@ namespace TagLib { * Implements the unified tag dictionary interface -- export function. * APE tags are perfectly compatible with the dictionary interface because they * support both arbitrary tag names and multiple values. Currently only - * APE items of type *Text* are handled by the dictionary interface, while - * *Binary* and *Locator* items are simply ignored. + * APE items of type *Text* are handled by the dictionary interface; all *Binary* + * and *Locator* items will be put into the unsupportedData list and can be + * deleted on request using removeUnsupportedProperties(). The same happens + * to Text items if their key is invalid for PropertyMap (which should actually + * never happen). * * The only conversion done by this export function is to rename the APE tags - * TRACK to TRACKNUMBER and YEAR to DATE, respectively, in order to be compliant - * with the names used in other formats. + * TRACK to TRACKNUMBER, YEAR to DATE, and ALBUM ARTIST to ALBUMARTIST, respectively, + * in order to be compliant with the names used in other formats. */ - TagDict toDict() const; + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); /*! * Implements the unified tag dictionary interface -- import function. The same * comments as for the export function apply. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns a pointer to the tag's footer. diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index ec925d0f..352ee27e 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -138,29 +138,39 @@ TagLib::Tag *FLAC::File::tag() const return &d->tag; } -TagLib::TagDict FLAC::File::toDict(void) const +PropertyMap FLAC::File::properties() const { - // once Tag::toDict() is virtual, this case distinction could actually be done + // once Tag::properties() is virtual, this case distinction could actually be done // within TagUnion. - if (d->hasXiphComment) - return d->tag.access(XiphIndex, false)->toDict(); - if (d->hasID3v2) - return d->tag.access(ID3v2Index, false)->toDict(); - if (d->hasID3v1) - return d->tag.access(ID3v1Index, false)->toDict(); - return TagLib::TagDict(); + if(d->hasXiphComment) + return d->tag.access(XiphIndex, false)->properties(); + if(d->hasID3v2) + return d->tag.access(ID3v2Index, false)->properties(); + if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->properties(); + return PropertyMap(); } -void FLAC::File::fromDict(const TagDict &dict) +void FLAC::File::removeUnsupportedProperties(const StringList &unsupported) { - if (d->hasXiphComment) - d->tag.access(XiphIndex, false)->fromDict(dict); - else if (d->hasID3v2) - d->tag.access(ID3v2Index, false)->fromDict(dict); - else if (d->hasID3v1) - d->tag.access(ID3v1Index, false)->fromDict(dict); + if(d->hasXiphComment) + d->tag.access(XiphIndex, false)->removeUnsupportedProperties(unsupported); + if(d->hasID3v2) + d->tag.access(ID3v2Index, false)->removeUnsupportedProperties(unsupported); + if(d->hasID3v1) + d->tag.access(ID3v1Index, false)->removeUnsupportedProperties(unsupported); +} + +PropertyMap FLAC::File::setProperties(const PropertyMap &properties) +{ + if(d->hasXiphComment) + return d->tag.access(XiphIndex, false)->setProperties(properties); + else if(d->hasID3v2) + return d->tag.access(ID3v2Index, false)->setProperties(properties); + else if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->setProperties(properties); else - d->tag.access(XiphIndex, true)->fromDict(dict); + return d->tag.access(XiphIndex, true)->setProperties(properties); } FLAC::Properties *FLAC::File::audioProperties() const diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 9fdc1c2e..b2ecce22 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -119,19 +119,21 @@ namespace TagLib { virtual TagLib::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. + * Implements the unified property interface -- export function. * If the file contains more than one tag (e.g. XiphComment and ID3v1), * only the first one (in the order XiphComment, ID3v2, ID3v1) will be - * converted to the TagDict. + * converted to the PropertyMap. */ - TagDict toDict() const; + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &); /*! - * Implements the unified tag dictionary interface -- import function. + * Implements the unified property interface -- import function. * As with the export, only one tag is taken into account. If the file * has no tag at all, a XiphComment will be created. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the FLAC::Properties for this file. If no audio properties diff --git a/taglib/it/itfile.cpp b/taglib/it/itfile.cpp index 5815b98a..1c3474cb 100644 --- a/taglib/it/itfile.cpp +++ b/taglib/it/itfile.cpp @@ -65,14 +65,14 @@ Mod::Tag *IT::File::tag() const return &d->tag; } -TagDict IT::File::toDict() const +PropertyMap IT::File::properties() const { - return d->tag.toDict(); + return d->tag.properties(); } -void IT::File::fromDict(const TagDict &tagDict) +PropertyMap IT::File::setProperties(const PropertyMap &properties) { - d->tag.fromDict(tagDict); + return d->tag.setProperties(properties); } IT::Properties *IT::File::audioProperties() const diff --git a/taglib/it/itfile.h b/taglib/it/itfile.h index 81b6c157..9c507742 100644 --- a/taglib/it/itfile.h +++ b/taglib/it/itfile.h @@ -61,16 +61,16 @@ namespace TagLib { Mod::Tag *tag() const; /*! - * Forwards to Mod::Tag::toDict(). + * Forwards to Mod::Tag::properties(). * BIC: will be removed once File::toDict() is made virtual */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Forwards to Mod::Tag::fromDict(). - * BIC: will be removed once File::fromDict() is made virtual + * Forwards to Mod::Tag::setProperties(). + * BIC: will be removed once File::setProperties() is made virtual */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the IT::Properties for this file. If no audio properties diff --git a/taglib/mod/modfile.cpp b/taglib/mod/modfile.cpp index d25ecf27..a05c2137 100644 --- a/taglib/mod/modfile.cpp +++ b/taglib/mod/modfile.cpp @@ -70,14 +70,14 @@ Mod::Properties *Mod::File::audioProperties() const return &d->properties; } -TagDict Mod::File::toDict() const +PropertyMap Mod::File::properties() const { - return d->tag.toDict(); + return d->tag.properties(); } -void Mod::File::fromDict(const TagDict &tagDict) +PropertyMap Mod::File::setProperties(const PropertyMap &properties) { - d->tag.fromDict(tagDict); + return d->tag.setProperties(properties); } bool Mod::File::save() diff --git a/taglib/mod/modfile.h b/taglib/mod/modfile.h index 7b1119c6..9e79659c 100644 --- a/taglib/mod/modfile.h +++ b/taglib/mod/modfile.h @@ -62,16 +62,16 @@ namespace TagLib { Mod::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. - * Forwards to Mod::Tag::toDict(). + * Implements the unified property interface -- export function. + * Forwards to Mod::Tag::properties(). */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. - * Forwards to Mod::Tag::fromDict(). + * Implements the unified property interface -- import function. + * Forwards to Mod::Tag::setProperties(). */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the Mod::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/mod/modtag.cpp b/taglib/mod/modtag.cpp index 89c21f09..aaa2f16a 100644 --- a/taglib/mod/modtag.cpp +++ b/taglib/mod/modtag.cpp @@ -121,30 +121,46 @@ void Mod::Tag::setTrackerName(const String &trackerName) d->trackerName = trackerName; } -TagDict Mod::Tag::toDict() const +PropertyMap Mod::Tag::properties() const { - TagDict dict; - dict["TITLE"] = d->title; - dict["COMMENT"] = d->comment; - if (!(d->trackerName == String::null)) - dict["TRACKERNAME"] = d->trackerName; - return dict; + PropertyMap properties; + properties["TITLE"] = d->title; + properties["COMMENT"] = d->comment; + if(!(d->trackerName.isNull())) + properties["TRACKERNAME"] = d->trackerName; + return properties; } -void Mod::Tag::fromDict(const TagDict &tagDict) +PropertyMap Mod::Tag::setProperties(const PropertyMap &origProps) { - if (tagDict.contains("TITLE") && !tagDict["TITILE"].isEmpty()) - d->title = tagDict["TITLE"][0]; - else + PropertyMap properties(origProps); + properties.removeEmpty(); + StringList oneValueSet; + if(properties.contains("TITLE")) { + d->title = properties["TITLE"].front(); + oneValueSet.append("TITLE"); + } else d->title = String::null; - if (tagDict.contains("COMMENT") && !tagDict["COMMENT"].isEmpty()) - d->comment = tagDict["COMMENT"][0]; - else + if(properties.contains("COMMENT")) { + d->comment = properties["COMMENT"].front(); + oneValueSet.append("COMMENT"); + } else d->comment = String::null; - if (tagDict.contains("TRACKERNAME") && !tagDict["TRACKERNAME"].isEmpty()) - d->trackerName = tagDict["TRACKERNAME"][0]; - else + if(properties.contains("TRACKERNAME")) { + d->trackerName = properties["TRACKERNAME"].front(); + oneValueSet.append("TRACKERNAME"); + } else d->trackerName = String::null; + + // for each tag that has been set above, remove the first entry in the corresponding + // value list. The others will be returned as unsupported by this format. + for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) { + if(properties[*it].size() == 1) + properties.erase(*it); + else + properties[*it].erase( properties[*it].begin() ); + } + return properties; } diff --git a/taglib/mod/modtag.h b/taglib/mod/modtag.h index 70f6bfc1..c1a74b10 100644 --- a/taglib/mod/modtag.h +++ b/taglib/mod/modtag.h @@ -160,19 +160,20 @@ namespace TagLib { void setTrackerName(const String &trackerName); /*! - * Implements the unified tag dictionary interface -- export function. - * Since the module tag is very limited, the exported dict is as well. + * Implements the unified property interface -- export function. + * Since the module tag is very limited, the exported map is as well. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. + * Implements the unified property interface -- import function. * Because of the limitations of the module file tag, any tags besides * COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be - * ignored. Additionally, if the dict contains tags with multiple values, - * all but the first will be ignored. + * returened. Additionally, if the map contains tags with multiple values, + * all but the first will be contained in the returned map of unsupported + * properties. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); private: Tag(const Tag &); diff --git a/taglib/tag.cpp b/taglib/tag.cpp index 04d0c461..3cebd134 100644 --- a/taglib/tag.cpp +++ b/taglib/tag.cpp @@ -53,75 +53,101 @@ bool Tag::isEmpty() const track() == 0); } -TagDict Tag::toDict() const +PropertyMap Tag::properties() const { - TagDict dict; - if (!(title() == String::null)) - dict["TITLE"].append(title()); - if (!(artist() == String::null)) - dict["ARTIST"].append(artist()); - if (!(album() == String::null)) - dict["ALBUM"].append(album()); - if (!(comment() == String::null)) - dict["COMMENT"].append(comment()); - if (!(genre() == String::null)) - dict["GENRE"].append(genre()); - if (!(year() == 0)) - dict["DATE"].append(String::number(year())); - if (!(track() == 0)) - dict["TRACKNUMBER"].append(String::number(track())); - return dict; + PropertyMap map; + if(!(title().isNull())) + map["TITLE"].append(title()); + if(!(artist().isNull())) + map["ARTIST"].append(artist()); + if(!(album().isNull())) + map["ALBUM"].append(album()); + if(!(comment().isNull())) + map["COMMENT"].append(comment()); + if(!(genre().isNull())) + map["GENRE"].append(genre()); + if(!(year() == 0)) + map["DATE"].append(String::number(year())); + if(!(track() == 0)) + map["TRACKNUMBER"].append(String::number(track())); + return map; } -void Tag::fromDict(const TagDict &dict) +void Tag::removeUnsupportedProperties(const StringList&) { - if (dict.contains("TITLE") && dict["TITLE"].size() >= 1) - setTitle(dict["TITLE"].front()); - else +} + +PropertyMap Tag::setProperties(const PropertyMap &origProps) +{ + PropertyMap properties(origProps); + properties.removeEmpty(); + StringList oneValueSet; + // can this be simplified by using some preprocessor defines / function pointers? + if(properties.contains("TITLE")) { + setTitle(properties["TITLE"].front()); + oneValueSet.append("TITLE"); + } else setTitle(String::null); - if (dict.contains("ARTIST") && !dict["ARTIST"].isEmpty()) - setArtist(dict["ARTIST"].front()); - else + if(properties.contains("ARTIST")) { + setArtist(properties["ARTIST"].front()); + oneValueSet.append("ARTIST"); + } else setArtist(String::null); - if (dict.contains("ALBUM") && !dict["ALBUM"].isEmpty()) - setAlbum(dict["ALBUM"].front()); - else - setAlbum(String::null); + if(properties.contains("ALBUM")) { + setAlbum(properties["ALBUM"].front()); + oneValueSet.append("ALBUM"); + } else + setAlbum(String::null); - if (dict.contains("COMMENT") && !dict["COMMENT"].isEmpty()) - setComment(dict["COMMENT"].front()); - else + if(properties.contains("COMMENT")) { + setComment(properties["COMMENT"].front()); + oneValueSet.append("COMMENT"); + } else setComment(String::null); - if (dict.contains("GENRE") && !dict["GENRE"].isEmpty()) - setGenre(dict["GENRE"].front()); - else + if(properties.contains("GENRE")) { + setGenre(properties["GENRE"].front()); + oneValueSet.append("GENRE"); + } else setGenre(String::null); - if (dict.contains("DATE") && !dict["DATE"].isEmpty()) { + if(properties.contains("DATE")) { bool ok; - int date = dict["DATE"].front().toInt(&ok); - if (ok) + int date = properties["DATE"].front().toInt(&ok); + if(ok) { setYear(date); - else + oneValueSet.append("DATE"); + } else setYear(0); } else setYear(0); - if (dict.contains("TRACKNUMBER") && !dict["TRACKNUMBER"].isEmpty()) { + if(properties.contains("TRACKNUMBER")) { bool ok; - int track = dict["TRACKNUMBER"].front().toInt(&ok); - if (ok) + int track = properties["TRACKNUMBER"].front().toInt(&ok); + if(ok) { setTrack(track); - else + oneValueSet.append("TRACKNUMBER"); + } else setTrack(0); } else setYear(0); + + // for each tag that has been set above, remove the first entry in the corresponding + // value list. The others will be returned as unsupported by this format. + for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) { + if(properties[*it].size() == 1) + properties.erase(*it); + else + properties[*it].erase( properties[*it].begin() ); + } + return properties; } + void Tag::duplicate(const Tag *source, Tag *target, bool overwrite) // static { if(overwrite) { diff --git a/taglib/tag.h b/taglib/tag.h index 728c3980..76c9a82a 100644 --- a/taglib/tag.h +++ b/taglib/tag.h @@ -28,17 +28,9 @@ #include "taglib_export.h" #include "tstring.h" -#include "tmap.h" namespace TagLib { - /*! - * This is used for the unified dictionary interface: the tags of a file are - * represented as a dictionary mapping a string (the tag name) to a list of - * strings (the values). - */ - typedef Map TagDict; - //! A simple, generic interface to common audio meta data fields /*! @@ -49,6 +41,8 @@ namespace TagLib { * in TagLib::AudioProperties, TagLib::File and TagLib::FileRef. */ + class PropertyMap; + class TAGLIB_EXPORT Tag { public: @@ -59,20 +53,30 @@ namespace TagLib { virtual ~Tag(); /*! - * Unified tag dictionary interface -- export function. Converts the tags - * of the specific metadata format into a "human-readable" map of strings - * to lists of strings, being as precise as possible. + * Exports the tags of the file as dictionary mapping (human readable) tag + * names (Strings) to StringLists of tag values. + * The default implementation in this class considers only the usual built-in + * tags (artist, album, ...) and only one value per key. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Unified tag dictionary interface -- import function. Converts a map - * of strings to stringslists into the specific metadata format. Note that - * not all formats can store arbitrary tags and values, so data might - * be lost by this operation. Especially the default implementation handles - * only single values of the default tags specified in this class. + * Removes unsupported properties, or a subset of them, from the tag. + * The parameter \a properties must contain only entries from + * properties().unsupportedData(). + * BIC: Will become virtual in future releases. Currently the non-virtual + * standard implementation of TagLib::Tag does nothing, since there are + * no unsupported elements. */ - void fromDict(const TagDict &); + void removeUnsupportedProperties(const StringList& properties); + + /*! + * Sets the tags of this File to those specified in \a properties. This default + * implementation sets only the tags for which setter methods exist in this class + * (artist, album, ...), and only one value per key; the rest will be contained + * in the returned PropertyMap. + */ + PropertyMap setProperties(const PropertyMap &properties); /*! * Returns the track name; if no track name is present in the tag diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index b08efbec..639bc0ec 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -113,83 +113,116 @@ FileName File::name() const return d->stream->name(); } -TagDict File::toDict() const +PropertyMap File::properties() const { // ugly workaround until this method is virtual if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); // no specialized implementation available -> use generic one // - ASF: ugly format, largely undocumented, not worth implementing // dict interface ... // - MP4: taglib's MP4::Tag does not really support anything beyond // the basic implementation, therefor we use just the default Tag // interface - return tag()->toDict(); + return tag()->properties(); } -void File::fromDict(const TagDict &dict) +void File::removeUnsupportedProperties(const StringList &properties) +{ + // here we only consider those formats that could possibly contain + // unsupported properties + if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else + tag()->removeUnsupportedProperties(properties); +} + +PropertyMap File::setProperties(const PropertyMap &properties) { if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else - tag()->fromDict(dict); - + return tag()->setProperties(properties); } ByteVector File::readBlock(ulong length) diff --git a/taglib/toolkit/tfile.h b/taglib/toolkit/tfile.h index 75faf8a8..7df774a0 100644 --- a/taglib/toolkit/tfile.h +++ b/taglib/toolkit/tfile.h @@ -37,6 +37,7 @@ namespace TagLib { class String; class Tag; class AudioProperties; + class PropertyMap; //! A file class with some useful methods for tag manipulation @@ -81,16 +82,32 @@ namespace TagLib { * Exports the tags of the file as dictionary mapping (human readable) tag * names (Strings) to StringLists of tag values. Calls the according specialization * in the File subclasses. - * Will be made virtual in future releases. + * For each metadata object of the file that could not be parsed into the PropertyMap + * format, the returend map's unsupportedData() list will contain one entry identifying + * that object (e.g. the frame type for ID3v2 tags). Use removeUnsupportedProperties() + * to remove (a subset of) them. + * BIC: Will be made virtual in future releases. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Sets the tags of this File to those specified by the given TagDict. Calls the + * Removes unsupported properties, or a subset of them, from the file's metadata. + * The parameter \a properties must contain only entries from + * properties().unsupportedData(). + * BIC: Will be mad virtual in future releases. + */ + void removeUnsupportedProperties(const StringList& properties); + + /*! + * Sets the tags of this File to those specified in \a properties. Calls the * according specialization method in the subclasses of File to do the translation * into the format-specific details. + * If some value(s) could not be written imported to the specific metadata format, + * the returned PropertyMap will contain those value(s). Otherwise it will be empty, + * indicating that no problems occured. + * BIC: will become pure virtual in the future */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &properties); /*! * Returns a pointer to this file's audio properties. This should be * reimplemented in the concrete subclasses. If no audio properties were diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index 00aeca88..2e043a33 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -40,11 +40,11 @@ PropertyMap::~PropertyMap() bool PropertyMap::insert(const String &key, const StringList &values) { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return false; Iterator result = supertype::find(realKey); - if (result == end()) + if(result == end()) supertype::insert(realKey, values); else supertype::operator[](realKey).append(values); @@ -54,7 +54,7 @@ bool PropertyMap::insert(const String &key, const StringList &values) bool PropertyMap::replace(const String &key, const StringList &values) { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return false; supertype::erase(realKey); supertype::insert(realKey, values); @@ -64,7 +64,7 @@ bool PropertyMap::replace(const String &key, const StringList &values) PropertyMap::Iterator PropertyMap::find(const String &key) { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return end(); return supertype::find(realKey); } @@ -72,7 +72,7 @@ PropertyMap::Iterator PropertyMap::find(const String &key) PropertyMap::ConstIterator PropertyMap::find(const String &key) const { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return end(); return supertype::find(realKey); } @@ -80,7 +80,8 @@ PropertyMap::ConstIterator PropertyMap::find(const String &key) const bool PropertyMap::contains(const String &key) const { String realKey = prepareKey(key); - if (realKey.isNull()) + // we consider keys with empty value list as not present + if(realKey.isNull() || supertype::operator[](realKey).isEmpty()) return false; return supertype::contains(realKey); } @@ -109,13 +110,23 @@ StringList &PropertyMap::operator[](const String &key) return supertype::operator[](realKey); } +void PropertyMap::removeEmpty() +{ + StringList emptyKeys; + for(Iterator it = begin(); it != end(); ++it) + if(it->second.isEmpty()) + emptyKeys.append(it->first); + for(StringList::Iterator emptyIt = emptyKeys.begin(); emptyIt != emptyKeys.end(); emptyIt++ ) + erase(*emptyIt); +} + StringList &PropertyMap::unsupportedData() { return unsupported; } -String PropertyMap::prepareKey(const String &proposed) const { - if (proposed.isEmpty()) +static String PropertyMap::prepareKey(const String &proposed) { + if(proposed.isEmpty()) return String::null; for (String::ConstIterator it = proposed.begin(); it != proposed.end(); it++) // forbid non-printable, non-ascii, '=' (#61) and '~' (#126) diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 2025873d..4f7bf555 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -39,6 +39,11 @@ namespace TagLib { * i.e. it must contain at least one character; all printable ASCII characters * except '=' and '~' are allowed. * + * In order to be safe with other formats, keep these additional restrictions in mind: + * + * - APE only allows keys from 2 to 16 printable ASCII characters (including space), + * with the exception of these strings: ID3, TAG, OggS, MP+ + * */ class TAGLIB_EXPORT PropertyMap: public Map @@ -117,13 +122,20 @@ namespace TagLib { */ StringList &unsupportedData(); - private: + /*! + * Removes all entries which have an empty value list. + */ + void removeEmpty(); /*! * Converts \a proposed into another String suitable to be used as * a key, or returns String::null if this is not possible. */ - String prepareKey(const String &proposed) const; + static String prepareKey(const String &proposed); + + private: + + StringList unsupported; };