d->textEncoding = encoding;
}
-PropertyMap CommentsFrame::asDescription() const
+PropertyMap CommentsFrame::asProperties() const
{
String key = PropertyMap::prepareKey(description());
PropertyMap map;
if(key.isNull())
map.unsupportedData().append(L"COMM/" + description());
else
- map.insert(key, text());
+ map.insert("COMMENT:" + key, text());
return map;
}
void setTextEncoding(String::Type encoding);
/*!
- * Parses this frame as PropertyMap.
- * - description() will be used as key
- * - if description() is empty, the key will be "COMMENT"
+ * Parses this frame as PropertyMap with a single key.
+ * - if description() is empty or "COMMENT", the key will be "COMMENT"
* - if description() is not a valid PropertyMap key, the frame will be
* marked unsupported by an entry "COMM/<description>" in the unsupportedData()
* attribute of the returned map.
+ * - otherwise, the key will be "COMMENT:<description>"
* - The single value will be the frame's text().
*/
- PropertyMap asDescription() const;
+ PropertyMap asProperties() const;
/*!
* Comments each have a unique description. This searches for a comment
TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const PropertyMap &properties) // static
{
- TextIdentificationFrame* frame = TextIdentificationFrame("TIPL");
+ TextIdentificationFrame *frame = TextIdentificationFrame("TIPL");
StringList l;
for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){
l.append(it->first);
frame->setText(l);
return frame;
}
+
+TextIdentificationFrame *TextIdentificationFrame::createTMCLFrame(const PropertyMap &properties) // static
+{
+ TextIdentificationFrame *frame = TextIdentificationFrame("TMCL");
+ StringList l;
+ for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){
+ if(!it->first.startsWith(instrumentPrefix)) // should not happen
+ continue;
+ l.append(it->first.substr(instrumentPrefix.size()));
+ l.append(it->second.toString(","));
+ }
+ frame->setText(l);
+ return frame;
+}
+
TextIdentificationFrame::~TextIdentificationFrame()
{
delete d;
*/
static TextIdentificationFrame *createTIPLFrame(const PropertyMap &properties);
+ /*!
+ * This is a special factory method to create a TMCL (musician credits list)
+ * frame from the given \a properties. Will parse key=[list of values] data
+ * into the TMCL format as specified in the ID3 standard, where key should be
+ * of the form instrumentPrefix:instrument.
+ */
+ static TextIdentificationFrame *createTMCLFrame(const PropertyMap &properties);
/*!
* Destroys this TextIdentificationFrame instance.
*/
* to the corresponding key used in a TIPL ID3 frame to describe that role.
*/
static const KeyConversionMap &involvedPeopleMap();
+
PropertyMap asProperties() const;
protected:
*/
explicit UserTextIdentificationFrame(const ByteVector &data);
+ /*!
+ * Creates a user defined text identification frame with the given \a description
+ * and \a text.
+ */
+ UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding = String::Latin1);
+
virtual String toString() const;
/*!
PropertyMap UnsynchronizedLyricsFrame::asProperties() const
{
+ String key = PropertyMap::prepareKey(description());
PropertyMap map;
- map.insert("LYRICS", text());
+ if(key.isEmpty())
+ key = "LYRICS";
+ if(key.isNull())
+ map.unsupportedData().append(L"USLT/" + description());
+ else
+ map.insert("LYRICS:" + key, text());
return map;
}
* Parses this frame as PropertyMap. The returned map will contain a single key
* "LYRICS" with the text() as single value.
*/
+ /*!
+ * Parses this frame as PropertyMap with a single key.
+ * - if description() is empty or "LYRICS", the key will be "LYRICS"
+ * - if description() is not a valid PropertyMap key, the frame will be
+ * marked unsupported by an entry "USLT/<description>" in the unsupportedData()
+ * attribute of the returned map.
+ * - otherwise, the key will be "LYRICS:<description>"
+ * - The single value will be the frame's text().
+ * Note that currently the language() field is not supported by the PropertyMap
+ * interface.
+ */
PropertyMap asProperties() const;
protected:
if(key.isNull())
map.unsupportedData().append(L"WXXX/" + description());
else
- map.insert(key, url());
+ map.insert("URL:" + key, url());
return map;
}
return d;
}
+String TextIdentificationFrame::instrumentPrefix("PERFORMER:");
+String TextIdentificationFrame::commentPrefix("COMMENT:");
+String TextIdentificationFrame::urlPrefix("URL:");
+
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
+Frame *Frame::createTextualFrame(const String &key, const StringList &values) //static
+{
+ // check if the key is contained in the key<=>frameID mapping
+ ByteVector frameID = keyToFrameID(key);
+ if(!frameID.isNull()) {
+ if(frameID[0] == 'T'){ // text frame
+ TextIdentificationFrame* frame = TextIdentificationFrame(frameID, String::UTF8);
+ frame->setText(values);
+ return frame;
+ } else if(values.size() == 1){ // URL frame (not WXXX); support only one value
+ UrlLinkFrame* frame = UrlLinkFrame(frameID);
+ frame->setUrl(values.front());
+ return frame;
+ }
+ }
+ // now we check if it's one of the "special" cases:
+ // -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS)
+ if(key == "LYRICS" && values.size() == 1){
+ UnsynchronizedLyricsFrame *frame = UnsynchronizedLyricsFrame();
+ frame->setText(values.front());
+ return frame;
+ }
+ // -URL: depending on the number of values, use WXXX or TXXX (with description=URL)
+ if((key == "URL" || key.startsWith(urlPrefix)) && values.size() == 1){
+ UserUrlLinkFrame *frame = UserUrlLinkFrame(String::UTF8);
+ frame->setDescription(key == "URL" ? key : key.substr(urlPrefix.size()));
+ frame->setUrl(values.front());
+ return frame;
+ }
+ // -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT)
+ if((key == "COMMENT" || key.startsWith(commentPrefix)) && values.size() == 1){
+ CommentsFrame *frame = CommentsFrame(String::UTF8);
+ frame->setDescription(key == "COMMENT" ? key : key.substr(commentPrefix.size()));
+ frame->setText(values.front());
+ return frame;
+ }
+ // if non of the above cases apply, we use a TXXX frame with the key as description
+ return UserTextIdentificationFrame(key, values, String::UTF8);
+}
+
Frame::~Frame()
{
delete d;
return checkEncoding(fields, encoding, header()->version());
}
-static const uint frameTranslationSize = 50;
+static const uint frameTranslationSize = 51;
static const char *frameTranslation[][2] = {
// Text information frames
{ "TALB", "ALBUM"},
{ "WPUB", "PUBLISHERWEBPAGE" },
//{ "WXXX", "URL"}, handled specially
// Other frames
- //{ "COMM", "COMMENT" }, handled specially
+ { "COMM", "COMMENT" },
//{ "USLT", "LYRICS" }, handled specially
};
return m;
}
}
+
+void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties,
+ PropertyMap &tiplProperties, PropertyMap &tmclProperties)
+{
+
+ singleFrameProperties.clear();
+ tiplProperties.clear();
+ tmclProperties.clear();
+ for(PropertyMap::ConstIterator it = original.begin(); it != original.end(); ++it) {
+ if(TextIdentificationFrame::involvedPeopleMap().contains(it->first))
+ tiplProperties.insert(it->first, it->second);
+ else if(it->first.startsWith(TextIdentificationFrame::instrumentPrefix))
+ tmclProperties.insert(it->first, it->second);
+ else
+ singleFrameProperties.insert(it->first, it->second);
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
// Frame::Header class
////////////////////////////////////////////////////////////////////////////////
friend class FrameFactory;
public:
+
+ /*!
+ * Creates a textual frame which corresponds to a single key in the PropertyMap
+ * interface. These are all (User)TextIdentificationFrames except TIPL and TMCL,
+ * all (User)URLLinkFrames, CommentsFrames, and UnsynchronizedLyricsFrame.
+ */
+ static Frame *createTextualFrame(const String &key, const StringList &values);
+
/*!
* Destroys this Frame instance.
*/
*/
static ByteVector textDelimiter(String::Type t);
+ /*!
+ * The string with which an instrument name is prefixed to build a key in a PropertyMap;
+ * used to translate PropertyMaps to TMCL frames. In the current implementation, this
+ * is "PERFORMER:".
+ */
+ static const String instrumentPrefix;
+ /*!
+ * The PropertyMap key prefix which triggers the use of a COMM frame instead of a TXXX
+ * frame for a non-standard key. In the current implementation, this is "COMMENT:".
+ */
+ static const String commentPrefix;
+ /*!
+ * The PropertyMap key prefix which triggers the use of a WXXX frame instead of a TXX
+ * frame for a non-standard key. In the current implementation, this is "URL:".
+ */
+ static const String urlPrefix;
+
protected:
class Header;
*/
static String frameIDToKey(const ByteVector &);
+
+ /*!
+ * This helper function splits the PropertyMap \a original into three ProperytMaps
+ * \a singleFrameProperties, \a tiplProperties, and \a tmclProperties, such that:
+ * - \a singleFrameProperties contains only of keys which can be represented with
+ * exactly one ID3 frame per key. In the current implementation
+ * this is everything except for the fixed "involved people" keys and keys of the
+ * form "TextIdentificationFrame::instrumentPrefix" + "instrument", which are
+ * mapped to a TMCL frame.
+ * - \a tiplProperties will consist of those keys that are present in
+ * TextIdentificationFrame::involvedPeopleMap()
+ * - \a tmclProperties contains the "musician credits" keys which should be mapped
+ * to a TMCL frame
+ */
+ static void splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties,
+ PropertyMap &tiplProperties, PropertyMap &tmclProperties);
+
private:
Frame(const Frame &);
Frame &operator=(const Frame &);
PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps)
{
- FrameList toRemove;
- PropertyMap properties = origProps;
- // first find out what frames to remove; we do not remove in-place
- // because that would invalidate FrameListMap iterators.
- // At the moment, we remove _all_ frames that don't contain unsupported data,
- // and create new ones in the next step; this is to avoid clumsy technicalities
- // arising when trying to do this more efficient. For example, if the current tag
- // contains one URL attribute stored in an WXXX frame, but the given \a properties
- // contain two URL values, we would need to remove the WXXX frame (which supports
- // only one value), and create a TXXX frame with description=URL.
- // The same may happen with COMM and USLT. Additionally, handling of TIPL and TMCL is
- // complicated.
- // In the future, someone might come up with a more clever sync algorithm. :-)
- for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) {
- String key = Frame::frameIDToKey(it->first);
- // for unsupported (binary) frame types, as well as frames that need special treatment
- // (TXXX, WXXX, COMM, TMCL, TIPL, USLT), key will be String::null
- if(key.isNull())
- continue;
- // else: non-user text or url frame -> there should be only one frame of this type,
- // and it's asProperties() method should return a PropertyMap with exactly one key
- // and empty unsupportedData().
- if(it->second.size() != 1)
- debug("invalid ID3 tag: found more than one " + it->first + " frame");
- PropertyMap frameMap = it->second[0]->asProperties();
- if(properties.contains(key) && frameMap[key] == properties[key])
- properties.erase(key);
- else
- toRemove.append(it->second[0]);
- }
-
- // now handle the special cases
- // first: TXXX frames
- FrameList &userTextFrames = frameList("TXXX");
- for(FrameList::ConstIterator it = userTextFrames.begin(); it != userTextFrames.end(); ++it) {
- PropertyMap frameMap = (*it)->asProperties();
- if(!frameMap.unsupportedData().isEmpty())
- // don't touch unsupported frames
- continue;
- // TXXX frames yield only one key, so it must be begin()->first
- String &key = frameMap.begin()->first;
- if(!Frame::keyToFrameID(key).isNull())
- // TXXX frame which a description (=key) for which there is a dedicated frame.
- // We don't want this, so remove the frame, the appropriate T*** or W*** frame
- // will be created later on.
- toRemove.append(*it);
- if(key.find(":") > 0)
- // colon-separated key: this should be inside a TMCL frame.
- toRemove.append(*it);
- // More (ugly) exceptions: If the user provides more than one COMMENT, LYRICS, or URL
- // tag, we store all of these in a TXXX, because COMM, USLT and WXXX. Otherwise there
- // should not be such a TXXX frame.
- if(key == "COMMENT") {
- if(properties.contains("COMMENT")
- && properties["COMMENT"].size() >= 2
- && properties["COMMENT"] == frameMap.begin()->second)
- properties.erase("COMMENT");
- else
- toRemove.append(*it);
- }else if(key == "LYRICS") {
- if(properties.contains("LYRICS")
- && properties["LYRICS"].size() >= 2
- && properties["LYRICS"] == frameMap.begin()->second)
- properties.erase("LYRICS");
+ FrameList framesToDelete;
+ // we split up the PropertyMap into the "normal" keys and the "complicated" ones,
+ // which are those according to TIPL or TMCL frames.
+ PropertyMap properties;
+ PropertyMap tiplProperties;
+ PropertyMap tmclProperties;
+ Frame::splitProperties(origProps, properties, tiplProperties, tmclProperties);
+ for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it){
+ for(FrameList::ConstIterator lit = it->second.begin(); lit != it->second.end(); ++lit){
+ PropertyMap frameProperties = (*lit)->asProperties();
+ if(it->first == "TIPL")
+ if (tiplProperties != frameProperties)
+ framesToDelete.append(*lit);
+ else
+ tiplProperties.erase(frameProperties);
+ else if(it->first == "TMCL")
+ if (tmclProperties != frameProperties)
+ framesToDelete.append(*lit);
+ else
+ tmclProperties.erase(frameProperties);
+ else if(!properties.contains(frameProperties))
+ framesToDelete.append(*lit);
else
- toRemove.append(*it);
- }else if(key == "URL") {
- if(properties.contains("URL")
- && properties["URL"].size() >= 2
- && properties["URL"] == frameMap.begin()->second)
- properties.erase("URL");
- else
- toRemove.append(*it);
+ properties.erase(frameProperties);
}
}
-
- // next: WXXX frames
- FrameList &userUrlFrames = frameList("WXXX");
- for(FrameList::ConstIterator it = userUrlFrames.begin(); it != userUrlFrames.end(); ++it) {
- PropertyMap frameMap = (*it)->asProperties();
- if(!frameMap.unsupportedData().isEmpty())
- // don't touch unsupported frames
- continue;
- // WXXX frames yield only one key, so it must be begin()->first
- String &key = frameMap.begin()->first;
- if(!Frame::keyToFrameID(key).isNull())
- // WXXX frame which a description (=key) for which there is a dedicated frame.
- // We don't want this, so remove the frame, the appropriate T*** or W*** frame
- // will be created later on.
- toRemove.append(*it);
- else if(key.find(":") > 0)
- // colon-separated key: this should be inside a TMCL frame.
- toRemove.append(*it);
- // More exceptions: we don't allow COMMENT and LYRICS in WXXX; they should be in COMM and USLT
- // (or TXXX, see above).
- else if(key == "COMMENT" || key == "LYRICS")
- toRemove.append(*it);
- // now, the key is either URL or some other string that neither has a dedicated text frame, nor
- // a colon. We accept the frame if it's contents match the values in properties. However, if
- // key != URL and the values are changed, they will be stored inside a TXXX frame instead, since
- // we can't distinguish free-form text from free-form URL keys (possible fix: use URL:REASON like
- // in TMCL / TIPL?).
- else if(properties.contains(key) && properties[key] == frameMap.begin()->second)
- properties.erase(key);
- else
- toRemove.append(*it);
- }
- for(FrameList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it)
+ for(FrameList::ConstIterator it = framesToDelete.begin(); it != framesToDelete.end(); ++it)
removeFrame(*it);
- // next: TIPL
- PropertyMap existingTipl;
- if(!frameList("TIPL").isEmpty())
- existingTipl = frameList("TIPL").front()->asProperties();
- PropertyMap requestedTipl;
- KeyConversionMap::ConstIterator it = TextIdentificationFrame::involvedPeopleMap().begin();
- bool rebuildTipl = false;
- for(; it != TextIdentificationFrame::involvedPeopleMap().end(); ++it) {
- if(properties.contains(it->first)){
- requestedTipl.insert(it->first, properties[it->first]);
- properties.erase(it->first); // it's ensured now that this key gets handled correctly
- if(!existingTipl.contains(it->first) || existingTipl[it->first] != requestedTipl[it->first])
- rebuildTipl = true;
- } else if(existingTipl.contains(it->first))
- rebuildTipl = true;
- }
- if(rebuildTipl){
- removeFrames("TIPL");
- addFrame(TextIdentificationFrame::createTIPLFrame(requestedTipl));
- }
-
- // next: create frames for everything still in properties except for TMCL ("PERFORMER:<instrument>")
- // keys, which are collected in a dedicated map
- PropertyMap requestedTmcl;
- for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){
- if(it->first.find(":") != -1)
- requestedTmcl.insert(it->first, it->second);
- else{
- // phew. Now we are in the simple case that our key=<value list> pair can be represented by a
- // single frame, either a T*** (not TIPL, TMCL) or W*** frame.
- ByteVector id = Frame::keyToFrameID(it->first);
- }
- }
-
- // next: TMCL
- PropertyMap existingTmcl;
- if(!frameList("TMCL").isEmpty())
- existingTmcl = frameList("TMCL").front()->asProperties();
- bool rebuildTmcl = false;
- // search for TMCL keys ("PERFORMER:<instrument>") in properties
- for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){
- if(it->first.find(":") != -1)
- requestedTmcl.insert(it->first, it->second);
- }
+ // now create remaining frames:
+ // start with the involved people list (TIPL)
+ if(!tiplProperties.isEmpty())
+ addFrame(TextIdentificationFrame::createTIPLFrame(tiplProperties));
+ // proceed with the musician credit list (TMCL)
+ if(!tmclProperties.isEmpty())
+ addFrame(TextIdentificationFrame::createTMCLFrame(tmclProperties));
+ // now create the "one key per frame" frames
+ for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it)
+ addFrame(Frame::createTextualFrame(it->first, it->second));
+ return PropertyMap; // ID3 implements the complete PropertyMap interface, so an empty map is returned
}
ByteVector ID3v2::Tag::render() const
return d->comment;
}
-TagLib::TagDict Ogg::FLAC::File::toDict(void) const
-{
- return d->comment->toDict();
-}
-
-void Ogg::FLAC::File::fromDict(const TagDict &dict)
-{
- d->comment->fromDict(dict);
-}
-
Properties *Ogg::FLAC::File::audioProperties() const
{
return d->properties;
*/
virtual XiphComment *tag() const;
- /*!
- * Implements the unified tag dictionary interface -- export function.
- * Returns the contents of the Ogg::XiphComment as TagDict.
- */
- TagDict toDict() const;
-
- /*!
- * Implements the unified tag dictionary interface -- import function.
- * Matches the TagDict's contents to the XiphComment of the file.
- */
- void fromDict(const TagDict &);
-
/*!
* Returns the FLAC::Properties for this file. If no audio properties
* were read then this will return a null pointer.
return d->comment;
}
-TagLib::TagDict Ogg::Speex::File::toDict(void) const
-{
- return d->comment->toDict();
-}
-
-void Ogg::Speex::File::fromDict(const TagDict &dict)
-{
- d->comment->fromDict(dict);
-}
-
Speex::Properties *Speex::File::audioProperties() const
{
return d->properties;
* TagLib::File::tag().
*/
virtual Ogg::XiphComment *tag() const;
- /*!
- * Implements the unified tag dictionary interface -- export function.
- * Returns the contents of the Ogg::XiphComment as TagDict.
- */
- TagDict toDict() const;
-
- /*!
- * Implements the unified tag dictionary interface -- import function.
- * Matches the TagDict's contents to the XiphComment of the file.
- */
- void fromDict(const TagDict &);
/*!
* Returns the Speex::Properties for this file. If no audio properties
return d->comment;
}
-TagLib::TagDict Ogg::Vorbis::File::toDict(void) const
+PropertyMap Vorbis::File::properties() const
{
- return d->comment->toDict();
+ return d->comment->properties();
}
-void Ogg::Vorbis::File::fromDict(const TagDict &dict)
+PropertyMap Vorbis::File::setProperties(const PropertyMap &properties)
{
- d->comment->fromDict(dict);
+ return d->comment->setProperties(properties);
}
Vorbis::Properties *Vorbis::File::audioProperties() const
*/
virtual Ogg::XiphComment *tag() const;
+
/*!
- * Implements the unified tag dictionary interface -- export function.
- * Returns the contents of the Ogg::XiphComment as TagDict.
+ * Implements the unified property interface -- export function.
+ * This forwards directly to XiphComment::properties().
*/
- TagDict toDict() const;
+ PropertyMap properties() const;
/*!
* Implements the unified tag dictionary interface -- import function.
- * Matches the TagDict's contents to the XiphComment of the file.
+ * Like properties(), this is a forwarder to the file's XiphComment.
*/
- void fromDict(const TagDict &);
+ PropertyMap setProperties(const PropertyMap &);
+
/*!
* Returns the Vorbis::Properties for this file. If no audio properties
* were read then this will return a null pointer.
return d->tag;
}
-TagLib::TagDict RIFF::AIFF::File::toDict(void) const
+PropertyMap RIFF::AIFF::File::properties() const
{
- return d->tag->toDict();
-
+ return d->tag->properties();
}
-void RIFF::AIFF::File::fromDict(const TagDict &dict)
+PropertyMap RIFF::AIFF::File::setProperties(const PropertyMap &properties)
{
- d->tag->fromDict(dict);
+ return d->tag->setProperties(properties);
}
virtual ID3v2::Tag *tag() const;
/*!
- * Implements the unified tag dictionary interface -- export function.
- * This method forwards to ID3v2::Tag::toDict.
+ * Implements the unified property interface -- export function.
+ * This method forwards to ID3v2::Tag::properties().
*/
- TagDict toDict() const;
+ PropertyMap properties() const;
/*!
- * Implements the unified tag dictionary interface -- import function.
- * This method forwards to ID3v2::Tag::fromDict.
+ * Implements the unified property interface -- import function.
+ * This method forwards to ID3v2::Tag::setProperties().
*/
- void fromDict(const TagDict &);
+ PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the AIFF::Properties for this file. If no audio properties
return d->tag;
}
-TagLib::TagDict RIFF::WAV::File::toDict(void) const
+PropertyMap RIFF::WAV::File::properties() const
{
- return d->tag->toDict();
+ return d->tag->properties();
}
-void RIFF::WAV::File::fromDict(const TagDict &dict)
+PropertyMap RIFF::WAV::File::setProperties(const PropertyMap &properties)
{
- d->tag->fromDict(dict);
+ return d->tag->setProperties(properties);
}
virtual ID3v2::Tag *tag() const;
/*!
- * Implements the unified tag dictionary interface -- export function.
- * This method forwards to ID3v2::Tag::toDict.
+ * Implements the unified property interface -- export function.
+ * This method forwards to ID3v2::Tag::properties().
*/
- TagDict toDict() const;
+ PropertyMap properties() const;
/*!
- * Implements the unified tag dictionary interface -- import function.
- * This method forwards to ID3v2::Tag::fromDict.
+ * Implements the unified property interface -- import function.
+ * This method forwards to ID3v2::Tag::setProperties().
*/
- void fromDict(const TagDict &);
+ PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the WAV::Properties for this file. If no audio properties
*/
bool contains(const String &key) const;
+ /*!
+ * Returns true if this map contains all keys of \a other
+ * and the values coincide for that keys.
+ */
+ bool contains(const PropertyMap &other) const;
+
/*!
* Erase the \a key and its values from the map.
*/
PropertyMap &erase(const String &key);
+ /*!
+ * Erases from this map all keys that appear in \a other.
+ */
+ PropertyMap &erase(const PropertyMap &other);
+
/*!
* Merge the contents of \a other into this PropertyMap.
* If a key is contained in both maps, the values of the second