]> granicus.if.org Git - taglib/commitdiff
Migration to new PropertyMap ... done ape to mod.
authorMichael Helmling <helmling@mathematik.uni-kl.de>
Sat, 21 Jan 2012 13:52:24 +0000 (14:52 +0100)
committerMichael Helmling <helmling@mathematik.uni-kl.de>
Sat, 21 Jan 2012 13:52:24 +0000 (14:52 +0100)
18 files changed:
taglib/ape/apefile.cpp
taglib/ape/apefile.h
taglib/ape/apetag.cpp
taglib/ape/apetag.h
taglib/flac/flacfile.cpp
taglib/flac/flacfile.h
taglib/it/itfile.cpp
taglib/it/itfile.h
taglib/mod/modfile.cpp
taglib/mod/modfile.h
taglib/mod/modtag.cpp
taglib/mod/modtag.h
taglib/tag.cpp
taglib/tag.h
taglib/toolkit/tfile.cpp
taglib/toolkit/tfile.h
taglib/toolkit/tpropertymap.cpp
taglib/toolkit/tpropertymap.h

index 7c63412ebb9a479057c7a69bd4b18a1b3a3c784b..6e806415a0870ed57a60d021690d0bc57c525e68 100644 (file)
@@ -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<APE::Tag>(APEIndex, false)->toDict();
-  if (d->hasID3v1)
-    return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->toDict();
-  return TagLib::TagDict();
+  if(d->hasAPE)
+    return d->tag.access<APE::Tag>(APEIndex, false)->properties();
+  if(d->hasID3v1)
+    return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->properties();
+  return PropertyMap();
+}
+
+void APE::File::removeUnsupportedProperties(const StringList &properties)
+{
+  if(d->hasAPE)
+    d->tag.access<APE::Tag>(APEIndex, false)->removeUnsupportedProperties(properties);
+  if(d->hasID3v1)
+    d->tag.access<ID3v1::Tag>(ID3v1Index, false)->removeUnsupportedProperties(properties);
 }
 
-void APE::File::fromDict(const TagDict &dict)
+PropertyMap APE::File::setProperties(const PropertyMap &properties)
 {
-  if (d->hasAPE)
-    d->tag.access<APE::Tag>(APEIndex, false)->fromDict(dict);
-  else if (d->hasID3v1)
-    d->tag.access<ID3v1::Tag>(ID3v1Index, false)->fromDict(dict);
+  if(d->hasAPE)
+    return d->tag.access<APE::Tag>(APEIndex, false)->setProperties(properties);
+  else if(d->hasID3v1)
+    return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->setProperties(properties);
   else
-    d->tag.access<APE::Tag>(APE, true)->fromDict(dict);
+    return d->tag.access<APE::Tag>(APE, true)->setProperties(properties);
 }
 
 APE::Properties *APE::File::audioProperties() const
index ab290b8372090d8727b7203a6b958f7ed56b1f17..0bdbd422c11cf9cb8ad08498db35605188947a9f 100644 (file)
@@ -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.
index e1bf40c2d954ad1e403ca64b72a426f06df8ac91..5393d72d79bc3fb2d22ce3844683f5189f39876d 100644 (file)
@@ -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
index 089420eaf41c400acb2910df3997326a2dbf608b..8520609ebc5cc19a8cc4a84e107acafbac92381e 100644 (file)
@@ -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.
index ec925d0fa1fc9eab80c993a0b82f3bbd5887f267..352ee27ed335e5db4fb3258b3e0ec7e78c7dafc8 100644 (file)
@@ -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<Ogg::XiphComment>(XiphIndex, false)->toDict();
-  if (d->hasID3v2)
-    return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->toDict();
-  if (d->hasID3v1)
-    return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->toDict();
-  return TagLib::TagDict();
+  if(d->hasXiphComment)
+    return d->tag.access<Ogg::XiphComment>(XiphIndex, false)->properties();
+  if(d->hasID3v2)
+    return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->properties();
+  if(d->hasID3v1)
+    return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->properties();
+  return PropertyMap();
+}
+
+void FLAC::File::removeUnsupportedProperties(const StringList &unsupported)
+{
+  if(d->hasXiphComment)
+    d->tag.access<Ogg::XiphComment>(XiphIndex, false)->removeUnsupportedProperties(unsupported);
+  if(d->hasID3v2)
+    d->tag.access<ID3v2::Tag>(ID3v2Index, false)->removeUnsupportedProperties(unsupported);
+  if(d->hasID3v1)
+    d->tag.access<ID3v1::Tag>(ID3v1Index, false)->removeUnsupportedProperties(unsupported);
 }
 
-void FLAC::File::fromDict(const TagDict &dict)
+PropertyMap FLAC::File::setProperties(const PropertyMap &properties)
 {
-  if (d->hasXiphComment)
-    d->tag.access<Ogg::XiphComment>(XiphIndex, false)->fromDict(dict);
-  else if (d->hasID3v2)
-    d->tag.access<ID3v2::Tag>(ID3v2Index, false)->fromDict(dict);
-  else if (d->hasID3v1)
-    d->tag.access<ID3v1::Tag>(ID3v1Index, false)->fromDict(dict);
+  if(d->hasXiphComment)
+    return d->tag.access<Ogg::XiphComment>(XiphIndex, false)->setProperties(properties);
+  else if(d->hasID3v2)
+    return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->setProperties(properties);
+  else if(d->hasID3v1)
+    return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->setProperties(properties);
   else
-    d->tag.access<Ogg::XiphComment>(XiphIndex, true)->fromDict(dict);
+    return d->tag.access<Ogg::XiphComment>(XiphIndex, true)->setProperties(properties);
 }
 
 FLAC::Properties *FLAC::File::audioProperties() const
index 9fdc1c2ebce34947a339f47a9d184d066c10e51f..b2ecce222540ff003c2d9aa6b13133b212a1fdd3 100644 (file)
@@ -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
index 5815b98adad304867b33f45e06ea75219fc730e2..1c3474cb6655033efd0ffcd0d540fda6923e8bc0 100644 (file)
@@ -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
index 81b6c157eba0a219400d3a9efe0b81400b85e4e5..9c5077428c46063c6a93141e2a29732d31591f75 100644 (file)
@@ -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
index d25ecf27cb3b42e0b4027dc8186c2a47ba394bd1..a05c21377f22e01870d8640557e8e413c17e95cc 100644 (file)
@@ -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()
index 7b1119c6329ae7cf2a9fa264516a85cd1ecf3cba..9e79659c4e285a4e742fedf6c08461055fedb34c 100644 (file)
@@ -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.
index 89c21f09bc882b51fe7c5fa8ba9c5e72db47fa4f..aaa2f16a7a17c4f5197202cf3ab412b6d744e092 100644 (file)
@@ -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;
 }
index 70f6bfc1725d6de23a57bd28a8de14ee68d48914..c1a74b1093d43ea33f45a73ffdcff2503cd72ec3 100644 (file)
@@ -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 &);
index 04d0c461787e82e2fea5999d1f7c6590ec691168..3cebd134f52a765382a85987d89a964cffe2a3b0 100644 (file)
@@ -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) {
index 728c3980867dad6235eef9c111ea122741537b1e..76c9a82af1c9ea841d97a1aac8615df0698f2cfe 100644 (file)
 
 #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<String, StringList> 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.
+     */
+    PropertyMap properties() const;
+
+    /*!
+     * 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.
      */
-    TagDict toDict() const;
+    void removeUnsupportedProperties(const StringList& properties);
 
     /*!
-     * 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.
+     * 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.
      */
-    void fromDict(const TagDict &);
+    PropertyMap setProperties(const PropertyMap &properties);
 
     /*!
      * Returns the track name; if no track name is present in the tag
index b08efbecab4e69ebe208a356f1e420691040249d..639bc0ecb90db1778e4b4400d56aed815d82a195 100644 (file)
@@ -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<const APE::File* >(this))
-    return dynamic_cast<const APE::File* >(this)->toDict();
+    return dynamic_cast<const APE::File* >(this)->properties();
   if (dynamic_cast<const FLAC::File* >(this))
-    return dynamic_cast<const FLAC::File* >(this)->toDict();
+    return dynamic_cast<const FLAC::File* >(this)->properties();
   if (dynamic_cast<const IT::File* >(this))
-    return dynamic_cast<const IT::File* >(this)->toDict();
+    return dynamic_cast<const IT::File* >(this)->properties();
   if (dynamic_cast<const Mod::File* >(this))
-    return dynamic_cast<const Mod::File* >(this)->toDict();
+    return dynamic_cast<const Mod::File* >(this)->properties();
   if (dynamic_cast<const MPC::File* >(this))
-    return dynamic_cast<const MPC::File* >(this)->toDict();
+    return dynamic_cast<const MPC::File* >(this)->properties();
   if (dynamic_cast<const MPEG::File* >(this))
-    return dynamic_cast<const MPEG::File* >(this)->toDict();
+    return dynamic_cast<const MPEG::File* >(this)->properties();
   if (dynamic_cast<const Ogg::FLAC::File* >(this))
-    return dynamic_cast<const Ogg::FLAC::File* >(this)->toDict();
+    return dynamic_cast<const Ogg::FLAC::File* >(this)->properties();
   if (dynamic_cast<const Ogg::Speex::File* >(this))
-    return dynamic_cast<const Ogg::Speex::File* >(this)->toDict();
+    return dynamic_cast<const Ogg::Speex::File* >(this)->properties();
   if (dynamic_cast<const Ogg::Vorbis::File* >(this))
-    return dynamic_cast<const Ogg::Vorbis::File* >(this)->toDict();
+    return dynamic_cast<const Ogg::Vorbis::File* >(this)->properties();
   if (dynamic_cast<const RIFF::AIFF::File* >(this))
-    return dynamic_cast<const RIFF::AIFF::File* >(this)->toDict();
+    return dynamic_cast<const RIFF::AIFF::File* >(this)->properties();
   if (dynamic_cast<const RIFF::WAV::File* >(this))
-    return dynamic_cast<const RIFF::WAV::File* >(this)->toDict();
+    return dynamic_cast<const RIFF::WAV::File* >(this)->properties();
   if (dynamic_cast<const S3M::File* >(this))
-    return dynamic_cast<const S3M::File* >(this)->toDict();
+    return dynamic_cast<const S3M::File* >(this)->properties();
   if (dynamic_cast<const TrueAudio::File* >(this))
-    return dynamic_cast<const TrueAudio::File* >(this)->toDict();
+    return dynamic_cast<const TrueAudio::File* >(this)->properties();
   if (dynamic_cast<const WavPack::File* >(this))
-    return dynamic_cast<const WavPack::File* >(this)->toDict();
+    return dynamic_cast<const WavPack::File* >(this)->properties();
   if (dynamic_cast<const XM::File* >(this))
-    return dynamic_cast<const XM::File* >(this)->toDict();
+    return dynamic_cast<const XM::File* >(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<APE::File* >(this))
-    dynamic_cast<APE::File* >(this)->fromDict(dict);
+    dynamic_cast<APE::File* >(this)->removeUnsupportedProperties(properties);
   else if (dynamic_cast<FLAC::File* >(this))
-    dynamic_cast<FLAC::File* >(this)->fromDict(dict);
+    dynamic_cast<FLAC::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<MPC::File* >(this))
+    dynamic_cast<MPC::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<MPEG::File* >(this))
+    dynamic_cast<MPEG::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<Ogg::FLAC::File* >(this))
+    dynamic_cast<Ogg::FLAC::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<Ogg::Speex::File* >(this))
+    dynamic_cast<Ogg::Speex::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<Ogg::Vorbis::File* >(this))
+    dynamic_cast<Ogg::Vorbis::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<RIFF::AIFF::File* >(this))
+    dynamic_cast<RIFF::AIFF::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<RIFF::WAV::File* >(this))
+    dynamic_cast<RIFF::WAV::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<S3M::File* >(this))
+    dynamic_cast<S3M::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<TrueAudio::File* >(this))
+    dynamic_cast<TrueAudio::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<WavPack::File* >(this))
+    dynamic_cast<WavPack::File* >(this)->removeUnsupportedProperties(properties);
+  else if (dynamic_cast<XM::File* >(this))
+    dynamic_cast<XM::File* >(this)->removeUnsupportedProperties(properties);
+  else
+    tag()->removeUnsupportedProperties(properties);
+}
+
+PropertyMap File::setProperties(const PropertyMap &properties)
+{
+  if (dynamic_cast<APE::File* >(this))
+    return dynamic_cast<APE::File* >(this)->setProperties(properties);
+  else if (dynamic_cast<FLAC::File* >(this))
+    return dynamic_cast<FLAC::File* >(this)->setProperties(properties);
   else if (dynamic_cast<IT::File* >(this))
-    dynamic_cast<IT::File* >(this)->fromDict(dict);
+    return dynamic_cast<IT::File* >(this)->setProperties(properties);
   else if (dynamic_cast<Mod::File* >(this))
-    dynamic_cast<Mod::File* >(this)->fromDict(dict);
+    return dynamic_cast<Mod::File* >(this)->setProperties(properties);
   else if (dynamic_cast<MPC::File* >(this))
-    dynamic_cast<MPC::File* >(this)->fromDict(dict);
+    return dynamic_cast<MPC::File* >(this)->setProperties(properties);
   else if (dynamic_cast<MPEG::File* >(this))
-    dynamic_cast<MPEG::File* >(this)->fromDict(dict);
+    return dynamic_cast<MPEG::File* >(this)->setProperties(properties);
   else if (dynamic_cast<Ogg::FLAC::File* >(this))
-    dynamic_cast<Ogg::FLAC::File* >(this)->fromDict(dict);
+    return dynamic_cast<Ogg::FLAC::File* >(this)->setProperties(properties);
   else if (dynamic_cast<Ogg::Speex::File* >(this))
-    dynamic_cast<Ogg::Speex::File* >(this)->fromDict(dict);
+    return dynamic_cast<Ogg::Speex::File* >(this)->setProperties(properties);
   else if (dynamic_cast<Ogg::Vorbis::File* >(this))
-    dynamic_cast<Ogg::Vorbis::File* >(this)->fromDict(dict);
+    return dynamic_cast<Ogg::Vorbis::File* >(this)->setProperties(properties);
   else if (dynamic_cast<RIFF::AIFF::File* >(this))
-    dynamic_cast<RIFF::AIFF::File* >(this)->fromDict(dict);
+    return dynamic_cast<RIFF::AIFF::File* >(this)->setProperties(properties);
   else if (dynamic_cast<RIFF::WAV::File* >(this))
-    dynamic_cast<RIFF::WAV::File* >(this)->fromDict(dict);
+    return dynamic_cast<RIFF::WAV::File* >(this)->setProperties(properties);
   else if (dynamic_cast<S3M::File* >(this))
-    dynamic_cast<S3M::File* >(this)->fromDict(dict);
+    return dynamic_cast<S3M::File* >(this)->setProperties(properties);
   else if (dynamic_cast<TrueAudio::File* >(this))
-    dynamic_cast<TrueAudio::File* >(this)->fromDict(dict);
+    return dynamic_cast<TrueAudio::File* >(this)->setProperties(properties);
   else if (dynamic_cast<WavPack::File* >(this))
-    dynamic_cast<WavPack::File* >(this)->fromDict(dict);
+    return dynamic_cast<WavPack::File* >(this)->setProperties(properties);
   else if (dynamic_cast<XM::File* >(this))
-    dynamic_cast<XM::File* >(this)->fromDict(dict);
+    return dynamic_cast<XM::File* >(this)->setProperties(properties);
   else
-    tag()->fromDict(dict);
-
+    return tag()->setProperties(properties);
 }
 
 ByteVector File::readBlock(ulong length)
index 75faf8a88029b2b316d12638d1d745ac1663b1f2..7df774a048060bf98d565ac66fcd05fe2350e4a4 100644 (file)
@@ -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
index 00aeca884c243b6939889666d6a288c91fa055e9..2e043a3393f5b408d0ca7694aa5ad6a50f8a44f8 100644 (file)
@@ -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)
index 2025873d4d105d2c779982c4eb840319878187ca..4f7bf555117b2e0c717f42e97329def6e123c0ee 100644 (file)
@@ -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<String,StringList>
@@ -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;
   };