]> granicus.if.org Git - taglib/commitdiff
Refactoring.
authorAllan Sandfeld Jensen <kde@carewolf.com>
Mon, 26 Jul 2004 19:03:28 +0000 (19:03 +0000)
committerAllan Sandfeld Jensen <kde@carewolf.com>
Mon, 26 Jul 2004 19:03:28 +0000 (19:03 +0000)
APE-tags are not native to musepack, so I am moving it out.
Seperated footer-decoding the same way header-decoding is seperated in
ID3v2.
Fixed a bug with the version in the footer. Should be 2000 (read 2.000) not 2.

git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@333011 283d02a7-25f6-0310-bc7c-ecb5cbfe19da

ape/Makefile.am [new file with mode: 0644]
ape/apefooter.cpp [new file with mode: 0644]
ape/apefooter.h [new file with mode: 0644]
ape/apetag.cpp [new file with mode: 0644]
ape/apetag.h [new file with mode: 0644]

diff --git a/ape/Makefile.am b/ape/Makefile.am
new file mode 100644 (file)
index 0000000..2563734
--- /dev/null
@@ -0,0 +1,13 @@
+INCLUDES = \
+       -I$(top_srcdir)/taglib \
+       -I$(top_srcdir)/taglib/toolkit \
+       $(all_includes)
+
+noinst_LTLIBRARIES = libape.la
+
+libape_la_SOURCES = apetag.cpp apefooter.cpp
+
+taglib_include_HEADERS = apetag.h apefooter.h
+taglib_includedir = $(includedir)/taglib
+
+EXTRA_DIST = $(libape_la_SOURCES) $(taglib_include_HEADERS)
diff --git a/ape/apefooter.cpp b/ape/apefooter.cpp
new file mode 100644 (file)
index 0000000..7d709de
--- /dev/null
@@ -0,0 +1,223 @@
+/***************************************************************************
+    copyright            : (C) 2004 by Allan Sandfeld Jensen
+                           (C) 2002, 2003 by Scott Wheeler (id3v2header.cpp)
+    email                : kde@carewolf.org
+ ***************************************************************************/
+
+/***************************************************************************
+ *   This library is free software; you can redistribute it and/or modify  *
+ *   it  under the terms of the GNU Lesser General Public License version  *
+ *   2.1 as published by the Free Software Foundation.                     *
+ *                                                                         *
+ *   This library is distributed in the hope that it will be useful, but   *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
+ *   Lesser General Public License for more details.                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU Lesser General Public      *
+ *   License along with this library; if not, write to the Free Software   *
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
+ *   USA                                                                   *
+ ***************************************************************************/
+
+#include <iostream>
+#include <bitset>
+
+#include <tstring.h>
+#include <tdebug.h>
+
+#include "apefooter.h"
+
+using namespace TagLib;
+using namespace APE;
+
+class Footer::FooterPrivate
+{
+public:
+  FooterPrivate() : version(0),
+                    footerPresent(true),
+                    headerPresent(false),
+                    isHeader(false),
+                    itemCount(0),
+                    tagSize(0) {}
+
+  ~FooterPrivate() {}
+
+  uint version;
+
+  bool footerPresent;
+  bool headerPresent;
+
+  bool isHeader;
+
+  uint itemCount;
+  uint tagSize;
+
+  static const uint size = 32;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// static members
+////////////////////////////////////////////////////////////////////////////////
+
+TagLib::uint Footer::size()
+{
+  return FooterPrivate::size;
+}
+
+ByteVector Footer::fileIdentifier()
+{
+  return ByteVector::fromCString("APETAGEX");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+Footer::Footer()
+{
+  d = new FooterPrivate;
+}
+
+Footer::Footer(const ByteVector &data)
+{
+  d = new FooterPrivate;
+  parse(data);
+}
+
+Footer::~Footer()
+{
+  delete d;
+}
+
+TagLib::uint Footer::version() const
+{
+  return d->version;
+}
+
+bool Footer::headerPresent() const
+{
+  return d->headerPresent;
+}
+
+bool Footer::footerPresent() const
+{
+  return d->footerPresent;
+}
+
+bool Footer::isHeader() const
+{
+  return d->isHeader;
+}
+
+void Footer::setHeaderPresent(bool b) const
+{
+  d->headerPresent = b;
+}
+
+TagLib::uint Footer::itemCount() const
+{
+  return d->itemCount;
+}
+
+void Footer::setItemCount(uint s)
+{
+  d->itemCount = s;
+}
+
+TagLib::uint Footer::tagSize() const
+{
+  return d->tagSize;
+}
+
+TagLib::uint Footer::completeTagSize() const
+{
+  if(d->headerPresent)
+    return d->tagSize + d->size;
+  else
+    return d->tagSize;
+}
+
+void Footer::setTagSize(uint s)
+{
+  d->tagSize = s;
+}
+
+void Footer::setData(const ByteVector &data)
+{
+  parse(data);
+}
+
+ByteVector Footer::renderFooter() const
+{
+    return render(false);
+}
+
+ByteVector Footer::renderHeader() const
+{
+    if (!d->headerPresent) return ByteVector();
+
+    return render(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// protected members
+////////////////////////////////////////////////////////////////////////////////
+
+void Footer::parse(const ByteVector &data)
+{
+  if(data.size() < size())
+    return;
+
+  // The first eight bytes, data[0..7], are the File Identifier, "APETAGEX".
+
+  // Read the version number
+  d->version = data.mid(8, 4).toUInt(false);
+
+  // Read the tag size
+  d->tagSize = data.mid(12, 4).toUInt(false);
+
+  // Read the item count
+  d->itemCount = data.mid(16, 4).toUInt(false);
+
+  // Read the flags
+  std::bitset<32> flags(data.mid(8, 4).toUInt(false));
+
+  d->headerPresent         = flags[31];
+  d->footerPresent         = !flags[30];
+  d->isHeader              = flags[29];
+
+}
+
+ByteVector Footer::render(bool isHeader) const
+{
+  ByteVector v;
+
+  // add the file identifier -- "APETAGEX"
+  v.append(fileIdentifier());
+
+  // add the version number -- we always render a 2.000 tag regardless of what
+  // the tag originally was.
+
+  v.append(ByteVector::fromUInt(2000, false));
+
+  // add the tag size
+  v.append(ByteVector::fromUInt(d->tagSize, false));
+
+  // add the item count
+  v.append(ByteVector::fromUInt(d->itemCount, false));
+
+  // render and add the flags
+  std::bitset<32> flags;
+
+  flags[31] = d->headerPresent;
+  flags[30] = false; // footer is always present
+  flags[29] = isHeader;
+
+  v.append(ByteVector::fromUInt(flags.to_ulong(), false));
+
+  // add the reserved 64bit
+  v.append(ByteVector::fromLongLong(0));
+
+  return v;
+}
diff --git a/ape/apefooter.h b/ape/apefooter.h
new file mode 100644 (file)
index 0000000..b3359d1
--- /dev/null
@@ -0,0 +1,168 @@
+/***************************************************************************
+    copyright            : (C) 2004 by Allan Sandfeld Jensen
+    email                : kde@carewolf.org
+ ***************************************************************************/
+
+/***************************************************************************
+ *   This library is free software; you can redistribute it and/or modify  *
+ *   it  under the terms of the GNU Lesser General Public License version  *
+ *   2.1 as published by the Free Software Foundation.                     *
+ *                                                                         *
+ *   This library is distributed in the hope that it will be useful, but   *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
+ *   Lesser General Public License for more details.                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU Lesser General Public      *
+ *   License along with this library; if not, write to the Free Software   *
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
+ *   USA                                                                   *
+ ***************************************************************************/
+
+#ifndef TAGLIB_APEFOOTER_H
+#define TAGLIB_APEFOOTER_H
+
+#include <tbytevector.h>
+
+namespace TagLib {
+
+  namespace APE {
+
+    //! An implementation of APE footers
+
+    /*!
+     * This class implements APE footers (and headers). It attempts to follow, both
+     * semantically and programatically, the structure specified in
+     * the APE v2.0 standard.  The API is based on the properties of APE footer and
+     * headers specified there.
+     */
+
+    class Footer
+    {
+    public:
+      /*!
+       * Constructs an empty APE footer.
+       */
+      Footer();
+
+      /*!
+       * Constructs an APE footer based on \a data.  parse() is called
+       * immediately.
+       */
+      Footer(const ByteVector &data);
+
+      /*!
+       * Destroys the footer.
+       */
+      virtual ~Footer();
+
+      /*!
+       * Returns the version number.  (Note: This is the 1000 or 2000.)
+       */
+      uint version() const;
+
+      /*!
+       * Returns true if a header is present in the tag.
+       */
+      bool headerPresent() const;
+
+      /*!
+       * Returns true if a footer is present in the tag.
+       */
+      bool footerPresent() const;
+
+      /*!
+       * Returns true this is actually the header.
+       */
+      bool isHeader() const;
+
+      /*!
+       * Sets whether the header should be rendered or not
+       */
+      void setHeaderPresent(bool b) const;
+
+      /*!
+       * Returns the number of items in the tag.
+       */
+      uint itemCount() const;
+
+      /*!
+       * Set the item count to \a s.
+       * \see itemCount()
+       */
+      void setItemCount(uint s);
+
+      /*!
+       * Returns the tag size in bytes.  This is the size of the frame content and footer.
+       * The size of the \e entire tag will be this plus the header size, if present.
+       *
+       * \see completeTagSize()
+       */
+      uint tagSize() const;
+
+      /*!
+       * Returns the tag size, including if present, the header
+       * size.
+       *
+       * \see tagSize()
+       */
+      uint completeTagSize() const;
+
+      /*!
+       * Set the tag size to \a s.
+       * \see tagSize()
+       */
+      void setTagSize(uint s);
+
+      /*!
+       * Returns the size of the footer.  Presently this is always 32 bytes.
+       */
+      static uint size();
+
+      /*!
+       * Returns the string used to identify an APE tag inside of a file.
+       * Presently this is always "APETAGEX".
+       */
+      static ByteVector fileIdentifier();
+
+      /*!
+       * Sets the data that will be used as the footer.  32 bytes,
+       * starting from \a data will be used.
+       */
+      void setData(const ByteVector &data);
+
+      /*!
+       * Renders the footer back to binary format.
+       */
+      ByteVector renderFooter() const;
+
+      /*!
+       * Renders the header corresponding to the footer. If headerPresent is
+       * set to false, it returns an empty ByteVector.
+       */
+      ByteVector renderHeader() const;
+
+    protected:
+      /*!
+       * Called by setData() to parse the footer data.  It makes this information
+       * available through the public API.
+       */
+      void parse(const ByteVector &data);
+
+      /*!
+       * Called by renderFooter and renderHeader
+       */
+      ByteVector render(bool isHeader) const;
+
+    private:
+      Footer(const Footer &);
+      Footer &operator=(const Footer &);
+
+      class FooterPrivate;
+      FooterPrivate *d;
+    };
+
+  }
+}
+
+#endif
diff --git a/ape/apetag.cpp b/ape/apetag.cpp
new file mode 100644 (file)
index 0000000..d18c387
--- /dev/null
@@ -0,0 +1,328 @@
+/***************************************************************************
+    copyright            : (C) 2004 by Allan Sandfeld Jensen
+    email                : kde@carewolf.com
+ ***************************************************************************/
+
+/***************************************************************************
+ *   This library is free software; you can redistribute it and/or modify  *
+ *   it  under the terms of the GNU Lesser General Public License version  *
+ *   2.1 as published by the Free Software Foundation.                     *
+ *                                                                         *
+ *   This library is distributed in the hope that it will be useful, but   *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
+ *   Lesser General Public License for more details.                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU Lesser General Public      *
+ *   License along with this library; if not, write to the Free Software   *
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
+ *   USA                                                                   *
+ ***************************************************************************/
+
+#include <tdebug.h>
+#include <tfile.h>
+#include <tstring.h>
+#include <tmap.h>
+
+#include "apetag.h"
+#include "apefooter.h"
+
+using namespace TagLib;
+using namespace APE;
+
+class APE::Tag::TagPrivate
+{
+public:
+  TagPrivate() : file(0), tagOffset(-1), tagLength(0) {}
+
+  File *file;
+  long tagOffset;
+  long tagLength;
+
+  Footer footer;
+
+  ItemListMap itemListMap;
+  Map<const String, ByteVector> binaries;
+};
+
+APE::Item::Item(const String& str) : readOnly(false), locator(false) {
+  value.append(str);
+}
+
+APE::Item::Item(const StringList& values) : readOnly(false), locator(false) {
+  value.append(values);
+}
+
+bool APE::Item::isEmpty() const
+{
+  return value.isEmpty();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public methods
+////////////////////////////////////////////////////////////////////////////////
+
+APE::Tag::Tag() : TagLib::Tag()
+{
+  d = new TagPrivate;
+}
+
+APE::Tag::Tag(File *file, long tagOffset) : TagLib::Tag()
+{
+  d = new TagPrivate;
+  d->file = file;
+  d->tagOffset = tagOffset;
+
+  read();
+}
+
+APE::Tag::~Tag()
+{
+  delete d;
+}
+
+using TagLib::uint;
+
+static ByteVector _render_APEItem(String key, Item item)
+{
+  ByteVector data;
+  uint flags = ((item.readOnly) ? 1 : 0) | ((item.locator) ? 2 : 0);
+  ByteVector value;
+
+  if (item.value.isEmpty()) return data;
+
+  StringList::Iterator it = item.value.begin();
+  value.append(it->data(String::UTF8));
+  it++;
+  while (it != item.value.end()) {
+    value.append('\0');
+    value.append(it->data(String::UTF8));
+    it++;
+  }
+
+  data.append(ByteVector::fromUInt(value.size(), false));
+  data.append(ByteVector::fromUInt(flags, false));
+  data.append(key.data(String::UTF8));
+  data.append(ByteVector('\0'));
+  data.append(value);
+
+  return data;
+}
+
+ByteVector APE::Tag::render() const
+{
+  ByteVector data;
+  uint itemCount = 0;
+
+  { Map<const String,Item>::Iterator i = d->itemListMap.begin();
+    while (i != d->itemListMap.end()) {
+      if (!i->second.value.isEmpty()) {
+        data.append(_render_APEItem(i->first, i->second));
+        itemCount++;
+      }
+      i++;
+    }
+  }
+
+  { Map<String,ByteVector>::Iterator i = d->binaries.begin();
+    while (i != d->binaries.end()) {
+      if (!i->second.isEmpty()) {
+          data.append(i->second);
+          itemCount++;
+      }
+      i++;
+    }
+  }
+
+  d->footer.setItemCount(itemCount);
+  d->footer.setTagSize(data.size()+Footer::size());
+
+  return d->footer.renderHeader() + data + d->footer.renderFooter();
+}
+
+ByteVector APE::Tag::fileIdentifier()
+{
+  return ByteVector::fromCString("APETAGEX");
+}
+
+String APE::Tag::title() const
+{
+  if(d->itemListMap["TITLE"].isEmpty())
+    return String::null;
+  return d->itemListMap["TITLE"].value.front();
+}
+
+String APE::Tag::artist() const
+{
+  if(d->itemListMap["ARTIST"].isEmpty())
+    return String::null;
+  return d->itemListMap["ARTIST"].value.front();
+}
+
+String APE::Tag::album() const
+{
+  if(d->itemListMap["ALBUM"].isEmpty())
+    return String::null;
+  return d->itemListMap["ALBUM"].value.front();
+}
+
+String APE::Tag::comment() const
+{
+  if(d->itemListMap["COMMENT"].isEmpty())
+    return String::null;
+  return d->itemListMap["COMMENT"].value.front();
+}
+
+String APE::Tag::genre() const
+{
+  if(d->itemListMap["GENRE"].isEmpty())
+    return String::null;
+  return d->itemListMap["GENRE"].value.front();
+}
+
+TagLib::uint APE::Tag::year() const
+{
+  if(d->itemListMap["YEAR"].isEmpty())
+    return 0;
+  return d->itemListMap["YEAR"].value.front().toInt();
+}
+
+TagLib::uint APE::Tag::track() const
+{
+  if(d->itemListMap["TRACK"].isEmpty())
+    return 0;
+  return d->itemListMap["TRACK"].value.front().toInt();
+}
+
+void APE::Tag::setTitle(const String &s)
+{
+  addValue("TITLE", s, true);
+}
+
+void APE::Tag::setArtist(const String &s)
+{
+  addValue("ARTIST", s, true);
+}
+
+void APE::Tag::setAlbum(const String &s)
+{
+  addValue("ALBUM", s, true);
+}
+
+void APE::Tag::setComment(const String &s)
+{
+  addValue("COMMENT", s, true);
+}
+
+void APE::Tag::setGenre(const String &s)
+{
+  addValue("GENRE", s, true);
+}
+
+void APE::Tag::setYear(uint i)
+{
+  if(i <=0 )
+    removeItem("YEAR");
+  else
+    addValue("YEAR", String::number(i), true);
+}
+
+void APE::Tag::setTrack(uint i)
+{
+  if(i <=0 )
+    removeItem("TRACK");
+  else
+    addValue("TRACK", String::number(i), true);
+}
+
+APE::Footer* APE::Tag::footer() const
+{
+  return &d->footer;
+}
+
+const APE::ItemListMap& APE::Tag::itemListMap() const
+{
+  return d->itemListMap;
+}
+
+void APE::Tag::removeItem(const String &key) {
+  Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper());
+  if(it != d->itemListMap.end())
+    d->itemListMap.erase(it);
+}
+
+void APE::Tag::addValue(const String &key, const String &value, bool replace)
+{
+  if(replace)
+    removeItem(key);
+  if(!value.isEmpty()) {
+    Map<const String, Item>::Iterator it = d->itemListMap.find(key.upper());
+    if (it != d->itemListMap.end())
+      d->itemListMap[key].value.append(value);
+    else
+      setItem(key, Item(value));
+  }
+}
+
+void APE::Tag::setItem(const String &key, const Item &item)
+{
+  d->itemListMap.insert(key, item);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// protected methods
+////////////////////////////////////////////////////////////////////////////////
+
+void APE::Tag::read()
+{
+  if(d->file && d->file->isValid()) {
+
+    d->file->seek(d->tagOffset);
+    d->footer.setData(d->file->readBlock(Footer::size()));
+
+    if(d->footer.tagSize() == 0 || d->footer.tagSize() > d->file->length())
+      return;
+
+    d->file->seek(d->tagOffset + Footer::size() - d->footer.tagSize());
+    parse(d->file->readBlock(d->footer.tagSize() - Footer::size()), d->footer.itemCount());
+  }
+}
+
+static StringList _parse_APEString(ByteVector val)
+{
+    StringList value;
+    int pold = 0;
+    int p = val.find('\0');
+    while (p != -1) {
+        value.append(String(val.mid(pold, p), String::UTF8));
+        pold = p+1;
+        p = val.find('\0', pold);
+    };
+    value.append(String(val.mid(pold), String::UTF8));
+
+    return value;
+}
+
+void APE::Tag::parse(const ByteVector &data, uint count)
+{
+  uint pos = 0;
+  uint vallen, flags;
+  String key, value;
+  while(count > 0) {
+    vallen = data.mid(pos+0,4).toUInt(false);
+    flags = data.mid(pos+4,4).toUInt(false);
+    key = String(data.mid(pos+8), String::UTF8);
+    key = key.upper();
+    APE::Item item;
+
+    if (flags < 4 ) {
+      ByteVector val = data.mid(pos+8+key.size()+1, vallen);
+      d->itemListMap.insert(key, Item(_parse_APEString(val)));
+    } else {
+      d->binaries.insert(key,data.mid(pos, 8+key.size()+1+vallen));
+    }
+
+    pos += 8 + key.size() + 1 + vallen;
+    count--;
+  }
+}
diff --git a/ape/apetag.h b/ape/apetag.h
new file mode 100644 (file)
index 0000000..7c161fd
--- /dev/null
@@ -0,0 +1,156 @@
+/***************************************************************************
+    copyright            : (C) 2004 by Allan Sandfeld Jensen
+    email                : kde@carewolf.org
+ ***************************************************************************/
+
+/***************************************************************************
+ *   This library is free software; you can redistribute it and/or modify  *
+ *   it  under the terms of the GNU Lesser General Public License version  *
+ *   2.1 as published by the Free Software Foundation.                     *
+ *                                                                         *
+ *   This library is distributed in the hope that it will be useful, but   *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
+ *   Lesser General Public License for more details.                       *
+ *                                                                         *
+ *   You should have received a copy of the GNU Lesser General Public      *
+ *   License along with this library; if not, write to the Free Software   *
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
+ *   USA                                                                   *
+ ***************************************************************************/
+
+#ifndef TAGLIB_APETAG_H
+#define TAGLIB_APETAG_H
+
+#include "tag.h"
+#include "tbytevector.h"
+#include "tmap.h"
+#include "tstring.h"
+#include "tstringlist.h"
+
+namespace TagLib {
+
+  class File;
+
+  namespace APE {
+
+    class Footer;
+
+    /*!
+     * A non-binary APE-item.
+     */
+    struct Item {
+        Item() {};
+        explicit Item(const String&);
+        explicit Item(const StringList&);
+        bool readOnly;
+        bool locator; // URL to external data
+        StringList value;
+        bool isEmpty() const;
+    };
+
+    /*!
+     * A mapping between a list of item names, or keys, and the associated item.
+     *
+     * \see APE::Tag::itemListMap()
+     */
+    typedef Map<const String, Item> ItemListMap;
+
+
+  //! An APE tag implementation
+
+    class Tag : public TagLib::Tag
+    {
+    public:
+      /*!
+       * Create an APE tag with default values.
+       */
+      Tag();
+
+      /*!
+       * Create an APE tag and parse the data in \a file with APE footer at
+       * \a tagOffset.
+       */
+      Tag(File *file, long tagOffset);
+
+      /*!
+       * Destroys this Tag instance.
+       */
+      virtual ~Tag();
+
+      /*!
+       * Renders the in memory values to a ByteVector suitable for writing to
+       * the file.
+       */
+      ByteVector render() const;
+
+      /*!
+       * Returns the string "APETAGEX" suitable for usage in locating the tag in a
+       * file.
+       */
+      static ByteVector fileIdentifier();
+
+      // Reimplementations.
+
+      virtual String title() const;
+      virtual String artist() const;
+      virtual String album() const;
+      virtual String comment() const;
+      virtual String genre() const;
+      virtual uint year() const;
+      virtual uint track() const;
+
+      virtual void setTitle(const String &s);
+      virtual void setArtist(const String &s);
+      virtual void setAlbum(const String &s);
+      virtual void setComment(const String &s);
+      virtual void setGenre(const String &s);
+      virtual void setYear(uint i);
+      virtual void setTrack(uint i);
+
+      /*!
+       * Returns a pointer to the tag's footer.
+       */
+      Footer *footer() const;
+
+      const ItemListMap &itemListMap() const;
+
+      /*!
+       * Removes the \a key comment from the tag
+       */
+      void removeItem(const String &key);
+
+      /*!
+       * Adds to the item specified by \a key the data \a value.  If \a replace
+       * is true, then all of the other values on the same key will be removed
+       * first.
+       */
+      void addValue(const String &key, const String &value, bool replace = true);
+
+      /*!
+       * Sets the \a key comment to \a item.
+       */
+      void setItem(const String &key, const Item &item);
+
+    protected:
+
+      /*!
+       * Reads from the file specified in the constructor.
+       */
+      void read();
+      /*!
+       * Parses the body of the tag in \a data with \a count items.
+       */
+      void parse(const ByteVector &data, uint count);
+
+    private:
+      Tag(const Tag &);
+      Tag &operator=(const Tag &);
+
+      class TagPrivate;
+      TagPrivate *d;
+    };
+  }
+}
+
+#endif