From 114a878c64ce6f8223cfd22d76a20eb16d177e5e Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdevelop@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- lib/catalog/Mainpage.dox | 10 + lib/catalog/Makefile.am | 13 ++ lib/catalog/catalog.cpp | 462 +++++++++++++++++++++++++++++++++++++++++++++++ lib/catalog/catalog.h | 80 ++++++++ lib/catalog/tag.cpp | 127 +++++++++++++ lib/catalog/tag.h | 309 +++++++++++++++++++++++++++++++ 6 files changed, 1001 insertions(+) create mode 100644 lib/catalog/Mainpage.dox create mode 100644 lib/catalog/Makefile.am create mode 100644 lib/catalog/catalog.cpp create mode 100644 lib/catalog/catalog.h create mode 100644 lib/catalog/tag.cpp create mode 100644 lib/catalog/tag.h (limited to 'lib/catalog') diff --git a/lib/catalog/Mainpage.dox b/lib/catalog/Mainpage.dox new file mode 100644 index 00000000..f18b8ced --- /dev/null +++ b/lib/catalog/Mainpage.dox @@ -0,0 +1,10 @@ +/** +@mainpage The KDevelop Catalog Library + +This is the persistant symbol store library working with BerkeleyDb backend. + +Link with: -lkdevcatalog \$(KDEDIR)/kdevbdb/libdb.a + +Include path: -I\$(kde_includes)/kdevelop/catalog -I\$(KDEDIR)/kdevbdb/include +*/ + diff --git a/lib/catalog/Makefile.am b/lib/catalog/Makefile.am new file mode 100644 index 00000000..ff01a6f6 --- /dev/null +++ b/lib/catalog/Makefile.am @@ -0,0 +1,13 @@ +INCLUDES = $(all_includes) $(DB3INCLUDES) +lib_LTLIBRARIES = libkdevcatalog.la +libkdevcatalog_la_SOURCES = tag.cpp catalog.cpp +libkdevcatalog_la_LDFLAGS = -no-undefined $(all_libraries) $(DB3LDFLAGS) +libkdevcatalog_la_LIBADD = -l$(DB3LIB) $(LIB_KDECORE) $(LIB_QT) + +kdevcatalogincludedir = $(includedir)/kdevelop/catalog +kdevcataloginclude_HEADERS = catalog.h tag.h + +DOXYGEN_REFERENCES = dcop interfaces kdecore kdefx kdeui khtml kmdi kio kjs kparts kutils +DOXYGEN_PROJECTNAME = KDevelop Catalog Library +DOXYGEN_DOCDIRPREFIX = kdev +include ../../Doxyfile.am diff --git a/lib/catalog/catalog.cpp b/lib/catalog/catalog.cpp new file mode 100644 index 00000000..b3438f1e --- /dev/null +++ b/lib/catalog/catalog.cpp @@ -0,0 +1,462 @@ +/* This file is part of KDevelop + Copyright (C) 2003 Roberto Raggi + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ +#include "catalog.h" +#include +#include +#include +#include + +#include +#include + + +#include +#include +#include + +#include + +struct _Catalog_Private +{ + QString dbName; + + DB* dbp; + QMap indexList; + KRandomSequence rnd; + bool enabled; + + _Catalog_Private() + : dbp( 0 ), enabled( true ) + { + } + + bool hasIndex( const QCString& name ) const + { + return indexList.contains( name ); + } + + DB* index( const QCString& name ) + { + return indexList[ name ]; + } + + bool addItem( DB* dbp, const QCString& id, const Tag& tag ) + { + Q_ASSERT( dbp != 0 ); + + DBT key, data; + int ret; + + std::memset( &key, 0, sizeof(key) ); + std::memset( &data, 0, sizeof(data) ); + + QByteArray a1; + { + QDataStream stream( a1, IO_WriteOnly ); + stream << id; + key.data = a1.data(); + key.size = a1.size(); + } + + QByteArray a2; + { + QDataStream stream( a2, IO_WriteOnly ); + tag.store( stream ); + data.data = a2.data(); + data.size = a2.size(); + } + + ret = dbp->put( dbp, 0, &key, &data, 0 ); + + return ret == 0; + } + + bool addItem( DB* dbp, const QVariant& id, const QCString& v ) + { + Q_ASSERT( dbp != 0 ); + + DBT key, data; + int ret; + + std::memset( &key, 0, sizeof(key) ); + std::memset( &data, 0, sizeof(data) ); + + QByteArray a1; + { + QDataStream stream( a1, IO_WriteOnly ); + stream << id; + key.data = a1.data(); + key.size = a1.size(); + } + + QByteArray a2; + { + QDataStream stream( a2, IO_WriteOnly ); + stream << v; + data.data = a2.data(); + data.size = a2.size(); + } + + ret = dbp->put( dbp, 0, &key, &data, 0 ); + + return ret == 0; + } + +}; + + +/*! + \fn Catalog::Catalog + */ + Catalog::Catalog() + : d( new _Catalog_Private() ) +{ +} + +/*! + \fn Catalog::~Catalog + */ + Catalog::~Catalog() +{ + close(); + delete( d ); + d = 0; +} + +/*! + \fn Catalog::indexList() const + */ + QValueList Catalog::indexList() const +{ + QValueList l; + QMap::Iterator it = d->indexList.begin(); + while( it != d->indexList.end() ){ + l << it.key(); + ++it; + } + + return l; +} + +bool Catalog::enabled() const +{ + return d->enabled; +} + +void Catalog::setEnabled( bool isEnabled ) +{ + d->enabled = isEnabled; +} + +/*! + \fn Catalog::addIndex( const QString& name ) + @todo document these functions + */ + void Catalog::addIndex( const QCString& name ) +{ + Q_ASSERT( d->dbp != 0 ); + + QMap::Iterator it = d->indexList.find( name ); + if( it == d->indexList.end() ){ + DB* dbp = 0; + + int ret; + + if ((ret = db_create(&dbp, 0, 0)) != 0) { + kdDebug() << "db_create: " << db_strerror(ret) << endl; + return /*false*/; + } + + if ((ret = dbp->set_flags(dbp, DB_DUP | DB_DUPSORT)) != 0) { + dbp->err(dbp, ret, "set_flags: DB_DUP | DB_DUPSORT"); + dbp->close( dbp, 0 ); + return; + } + + QFileInfo fileInfo( d->dbName ); + QString indexName = fileInfo.dirPath(true) + "/" + fileInfo.baseName(true) + "." + QString(name) + ".idx"; + + if( (ret = dbp->set_cachesize( dbp, 0, 2 * 1024 * 1024, 0 )) != 0 ){ + kdDebug() << "set_cachesize: " << db_strerror(ret) << endl; + } + + if ((ret = dbp->open( + dbp, NULL, QFile::encodeName( indexName ).data(), 0, DB_BTREE, DB_CREATE, 0664)) != 0) { + kdDebug() << "db_open: " << db_strerror(ret) << endl; + dbp->close( dbp, 0 ); + return; + } + + d->indexList[ name ] = dbp; + } +} + +/*! + \fn Catalog::close() + */ + + void Catalog::close() +{ + d->dbName = QString::null; + + QMap::Iterator it = d->indexList.begin(); + while( it != d->indexList.end() ){ + if( it.data() ){ + it.data()->close( it.data(), 0 ); + } + ++it; + } + d->indexList.clear(); + + if( d->dbp != 0 ){ + d->dbp->close( d->dbp, 0 ); + d->dbp = 0; + } +} + +/*! + \fn Catalog::open( const QString& dbName ) + */ + + void Catalog::open( const QString& dbName ) +{ + Q_ASSERT( d->dbp == 0 ); + + d->dbName = dbName; + + int ret; + + if ((ret = db_create(&d->dbp, 0, 0)) != 0) { + kdDebug() << "db_create: " << db_strerror(ret) << endl; + return /*false*/; + } + + if ((ret = d->dbp->set_flags(d->dbp, DB_RECNUM)) != 0) { + d->dbp->err(d->dbp, ret, "set_flags: DB_RECNUM"); + close(); + return; + } + + if( (ret = d->dbp->set_cachesize( d->dbp, 0, 2 * 1024 * 1024, 0 )) != 0 ){ + kdDebug() << "set_cachesize: " << db_strerror(ret) << endl; + } + + if ((ret = d->dbp->open( + d->dbp, NULL, d->dbName.local8Bit(), 0, DB_BTREE, DB_CREATE, 0664)) != 0) { + kdDebug() << "db_open: " << db_strerror(ret) << endl; + close(); + return; + } +} + +/*! + \fn Catalog::dbName() const + */ + + QString Catalog::dbName() const +{ + return d->dbName; +} + +/*! + \fn Catalog::isValid() const + */ + + bool Catalog::isValid() const +{ + return d->dbp != 0; +} + +/*! + \fn Catalog::addItem( Tag& tag ) + */ + + void Catalog::addItem( Tag& tag ) +{ + if( tag.name().isEmpty() ) + return; + + QCString id = generateId(); + + tag.setId( id ); + if( d->addItem(d->dbp, id, tag) ){ + QMap::Iterator it = d->indexList.begin(); + while( it != d->indexList.end() ){ + if( tag.hasAttribute(it.key()) ) + d->addItem( it.data(), tag.attribute(it.key()), id ); + ++it; + } + } +} + +/*! + \fn Catalog::getItemById( const QString& id ) + */ + + Tag Catalog::getItemById( const QCString& id ) +{ + Q_ASSERT( d->dbp != 0 ); + + DBT key, data; + std::memset( &key, 0, sizeof(key) ); + std::memset( &data, 0, sizeof(data) ); + + QByteArray a1; + { + QDataStream stream( a1, IO_WriteOnly ); + stream << id; + key.data = a1.data(); + key.size = a1.size(); + } + + int ret = d->dbp->get( d->dbp, 0, &key, &data, 0 ); + Q_ASSERT( ret == 0 ); + + Tag tag; + + if( ret == 0 ){ + QByteArray a; + a.setRawData( (const char*) data.data, data.size ); + QDataStream stream( a, IO_ReadOnly ); + tag.load( stream ); + a.resetRawData( (const char*) data.data, data.size ); + } + + return tag; +} + +/*! + \fn Catalog::sync() +*/ + + void Catalog::sync() +{ + Q_ASSERT( d->dbp != 0 ); + d->dbp->sync( d->dbp, 0 ); + + QMap::Iterator it = d->indexList.begin(); + while( it != d->indexList.end() ){ + it.data()->sync( it.data(), 0 ); + ++it; + } +} + +/*! + \fn Catalog::query( const QValueList& args ) +*/ + + QValueList Catalog::query( const QValueList& args ) +{ + QValueList tags; + + DBT key, data; + + DBC** cursors = new DBC* [ args.size() + 1 ]; + + QValueList< QPair >::ConstIterator it = args.begin(); + int current = 0; + while( it != args.end() ){ + QCString indexName = (*it).first; + QVariant value = (*it).second; + + if( d->hasIndex(indexName) ) { + DB* dbp = d->index( indexName ); + Q_ASSERT( dbp != 0 ); + + std::memset( &key, 0, sizeof(key) ); + std::memset( &data, 0, sizeof(data) ); + + QByteArray a1; + { + QDataStream stream( a1, IO_WriteOnly ); + stream << value; + key.data = a1.data(); + key.size = a1.size(); + } + + DBC* cursor = 0; + int rtn = dbp->cursor( dbp, 0, &cursor, 0 ); + + if ( rtn == 0 ) { + + rtn = cursor->c_get( cursor, &key, &data, DB_SET ); + + if ( rtn == 0 ) { + cursors[ current++ ] = cursor; + } + else if ( rtn != DB_NOTFOUND) { + kdDebug() << "fetching cursor failed: " << db_strerror(rtn) << endl; + cursor->c_close(cursor); + } + } + else { + kdDebug() << "creating cursor failed: " << db_strerror(rtn) << endl; + } + } + ++it; + } + + cursors[ current ] = 0; + + if( current == args.size() ) { + + DBC* join_curs = 0; + int rtn = d->dbp->join( d->dbp, cursors, &join_curs, 0 ); + + if ( rtn == 0 ) { + + std::memset( &key, 0, sizeof(key) ); + std::memset( &data, 0, sizeof(data) ); + + while( join_curs->c_get(join_curs, &key, &data, 0) == 0 ) { + + QByteArray a2; + a2.setRawData( (const char*) data.data, data.size ); + QDataStream s( a2, IO_ReadOnly ); + Tag tag; + tag.load( s ); + a2.resetRawData( (const char*) data.data, data.size ); + tags << tag; + } + + join_curs->c_close( join_curs ); + } + else { + kdDebug() << "joining results failed: " << db_strerror(rtn) << endl; + } + } + + DBC** c = cursors; + while( *c != 0 ){ + (*c)->c_close( *c ); + ++c; + } + delete[] cursors; + + return tags; +} + + QCString Catalog::generateId() +{ + static int n = 1; + QCString asStr; + asStr.sprintf( "%05d", n++ ); + return asStr; +} + diff --git a/lib/catalog/catalog.h b/lib/catalog/catalog.h new file mode 100644 index 00000000..8183acaf --- /dev/null +++ b/lib/catalog/catalog.h @@ -0,0 +1,80 @@ +/* This file is part of KDevelop + Copyright (C) 2003 Roberto Raggi + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef CATALOG_H +#define CATALOG_H + +#include +#include +#include + + +#include "tag.h" +/** +@file catalog.h +Catalog database - the persistent symbol store database. +*/ + +/** +Catalog objects represent separate symbol databases. +Catalogs can be created/loaded/unloaded dynamically. +To find a symbol in the repository each catalog should be queried. + +Persistent symbol store is useful to keep information about code that +never or rarely changes. System libraries are perfect examples of such code. +*/ +class Catalog +{ +public: + typedef QPair QueryArgument; + +public: + Catalog(); + virtual ~Catalog(); + + bool isValid() const; + QString dbName() const; + + bool enabled() const; + void setEnabled( bool en ); + + virtual void open( const QString& dbName ); + virtual void close(); + virtual void sync(); + + QValueList indexList() const; + void addIndex( const QCString& name ); + + void addItem( Tag& tag ); + + Tag getItemById( const QCString& id ); + QValueList query( const QValueList& args ); + + QCString generateId(); + +private: + class _Catalog_Private* d; + +private: + Catalog( const Catalog& source ); + void operator = ( const Catalog& source ); +}; + + +#endif diff --git a/lib/catalog/tag.cpp b/lib/catalog/tag.cpp new file mode 100644 index 00000000..f4dbda67 --- /dev/null +++ b/lib/catalog/tag.cpp @@ -0,0 +1,127 @@ +/* This file is part of KDevelop + Copyright (C) 2003 Roberto Raggi + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "tag.h" +#include + +Tag::Tag() +{ + data = new TagData(); + data->kind = 0; + data->flags = 0; + data->startLine = 0; + data->startColumn = 0; + data->endLine = 0; + data->endColumn = 0; +} + +Tag::Tag( const Tag& source ) +{ + data = source.data; + data->ref(); +} + +Tag::~Tag() +{ + if( data->deref() ){ + delete( data ); + data = 0; + } +} + +void Tag::detach() +{ + if( data->count != 1 ) + *this = copy(); +} + +Tag Tag::copy() +{ + Tag t; + + t.data->id = data->id; + t.data->kind = data->kind; + t.data->flags = data->flags; + t.data->name = data->name; + t.data->scope = data->scope; + t.data->fileName = data->fileName; + t.data->startLine = data->startLine; + t.data->startColumn = data->startColumn; + t.data->endLine = data->endLine; + t.data->endColumn = data->endColumn; + t.data->attributes = data->attributes; + + return t; +} + +Tag& Tag::operator = ( const Tag& source ) +{ + source.data->ref(); + if ( data->deref() ){ + delete data; + } + data = source.data; + + return( *this ); +} + +void Tag::load( QDataStream& stream ) +{ + stream + >> data->id + >> data->kind + >> data->flags + >> data->name + >> data->scope + >> data->fileName + >> data->startLine + >> data->startColumn + >> data->endLine + >> data->endColumn + >> data->attributes; +} + +void Tag::store( QDataStream& stream ) const +{ + stream + << data->id + << data->kind + << data->flags + << data->name + << data->scope + << data->fileName + << data->startLine + << data->startColumn + << data->endLine + << data->endColumn + << data->attributes; +} + +QDataStream& operator << ( QDataStream& s, const Tag& t) +{ + t.store( s ); + return s; +} + +QDataStream& operator >> ( QDataStream& s, Tag& t ) +{ + t.load( s ); + return s; +} + diff --git a/lib/catalog/tag.h b/lib/catalog/tag.h new file mode 100644 index 00000000..af5879a6 --- /dev/null +++ b/lib/catalog/tag.h @@ -0,0 +1,309 @@ +/* This file is part of KDevelop + Copyright (C) 2003 Roberto Raggi + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#ifndef TAG_H +#define TAG_H + +#include +#include +#include + +class QDataStream; + +union TagFlags +{ + unsigned long flags; + struct + { + unsigned long access: + 3; + unsigned long isVirtual: + 1; + } + data; +} ; + +class Tag +{ +public: + enum Kind + { + Kind_Unknown, + + Kind_Typedef = 1000, + Kind_Namespace, + Kind_UsingDirective, + Kind_Base_class, + Kind_Enum, + Kind_Enumerator, + Kind_Class, + Kind_Struct, + Kind_Union, + Kind_VariableDeclaration, + Kind_Variable, + Kind_FunctionDeclaration, + Kind_Function, + Kind_NamespaceAlias, + Kind_TranslationUnit, + + // ... + + Kind_Custom = 2000 + }; + +public: + Tag(); + Tag( const Tag& source ); + ~Tag(); + + operator bool() const { + return kind() != Kind_Unknown && kind() != 0; + } + + Tag& operator = ( const Tag& source ); + + QCString id() const + { + return data->id; + } + + void setId( const QCString& id ) + { + detach(); + data->id = id; + } + + int kind() const + { + return data->kind; + } + + void setKind( int kind ) + { + detach(); + data->kind = kind; + } + + unsigned long flags() const + { + return data->flags; + } + + void setFlags( unsigned long flags ) + { + detach(); + data->flags = flags; + } + + QString fileName() const + { + return data->fileName; + } + + void setFileName( const QString& fileName ) + { + detach(); + data->fileName = fileName; + } + + QString path( const QString& sep = QString::fromLatin1("::") ) const + { + QString s = scope().join( sep ); + if( s.isNull() ) + return name(); + return s + sep + name(); + } + + QString name() const + { + return data->name; + } + + QString comment() const { + if( hasAttribute( "cmt" ) ) { + return attribute( "cmt" ).asString(); + } else { + return ""; + } + } + + void setComment( const QString& comment ) { + setAttribute( "cmt", comment ); + } + + void setName( const QString& name ) + { + detach(); + data->name = name; + } + + QStringList scope() const + { + return data->scope; + } + + void setScope( const QStringList& scope ) + { + detach(); + data->scope = scope; + } + + void getStartPosition( int* line, int* column ) const + { + if( line ) *line = data->startLine; + if( column ) *column = data->startColumn; + } + + void setStartPosition( int line, int column ) + { + detach(); + data->startLine = line; + data->startColumn = column; + } + + void getEndPosition( int* line, int* column ) const + { + if( line ) *line = data->endLine; + if( column ) *column = data->endColumn; + } + + void setEndPosition( int line, int column ) + { + detach(); + data->endLine = line; + data->endColumn = column; + } + + QString getSpecializationDeclaration() const { + if( hasAttribute( "spc" ) ) + return data->attributes["spc"].asString(); + else + return QString::null; + } + + bool hasSpecializationDeclaration() const { + return data->attributes.contains( "spc" ); + } + + void setSpecializationDeclaration( const QString& str ) { + data->attributes["spc"] = str; + } + + bool hasAttribute( const QCString& name ) const + { + if( name == "kind" || + name == "name" || + name == "scope" || + name == "fileName" || + name == "startLine" || + name == "startColumn" || + name == "endLine" || + name == "endColumn" ) + return true; + return data->attributes.contains( name ); + } + + QVariant attribute( const QCString& name ) const + { + if( name == "id" ) + return data->id; + else if( name == "kind" ) + return data->kind; + else if( name == "name" ) + return data->name; + else if( name == "scope" ) + return data->scope; + else if( name == "fileName" ) + return data->fileName; + else if( name == "startLine" ) + return data->startLine; + else if( name == "startColumn" ) + return data->startColumn; + else if( name == "endLine" ) + return data->endLine; + else if( name == "endColumn" ) + return data->endColumn; + else if( name == "prefix" ) + return data->name.left( 2 ); + return data->attributes[ name ]; + } + + void setAttribute( const QCString& name, const QVariant& value ) + { + detach(); + if( name == "id" ) + data->id = value.toCString(); + else if( name == "kind" ) + data->kind = value.toInt(); + else if( name == "name" ) + data->name = value.toString(); + else if( name == "scope" ) + data->scope = value.toStringList(); + else if( name == "fileName" ) + data->fileName = value.toString(); + else if( name == "startLine" ) + data->startLine = value.toInt(); + else if( name == "startColumn" ) + data->startColumn = value.toInt(); + else if( name == "endLine" ) + data->endLine = value.toInt(); + else if( name == "endColumn" ) + data->endColumn = value.toInt(); + else + data->attributes[ name ] = value; + } + + void addTemplateParam( const QString& param , const QString& def = "" ) { + QMap::iterator it = data->attributes.find( "tpl" ); + if( it != data->attributes.end() && (*it).type() == QVariant::StringList ) { + }else{ + it = data->attributes.insert( "tpl", QVariant( QStringList() ) ); + } + + QStringList& l( (*it).asStringList() ); + l << param; + l << def; + } + + void load( QDataStream& stream ); + void store( QDataStream& stream ) const; + +private: + Tag copy(); + void detach(); + +private: + struct TagData: public QShared + { + QCString id; + int kind; + unsigned long flags; + QString name; + QStringList scope; + QString fileName; + int startLine, startColumn; + int endLine, endColumn; + QMap attributes; + } *data; +}; + +QDataStream& operator << ( QDataStream&, const Tag& ); +QDataStream& operator >> ( QDataStream&, Tag& ); + +#endif -- cgit v1.2.1