Source: fun/SerialTree.h


Annotated List
Files
Globals
Hierarchy
Index
//  SerialTree - a simple serializer & deserializer
//  Copyright (C) 2000-2003 bozo & sgbeal @users.sourceforge.net
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 2
//  of the License, or (at your option) any later version.
//
//  This program 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 General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// Note that any references to "I" in this file may be reference to either
// rusty or stephan.

#ifndef _FUN_SERIALTREE_H
#define _FUN_SERIALTREE_H


#include 
#include 
#include 
#include 
#include 
#include 
#include 
//#include   //  for QDomNode

class QDomDocument;
class QDomNode;
class QDomElement;
class QTextStream;

namespace fun {

class SerialTree;
/**
 * The templated deserialize() is NOT a member function because I couldn't get the compiler to allow 
 * a static template, declared as:
 *  static template  Deser *deserialize( SerialTree *tree, Deser *ptrToType, const char *classNameInSerialTree );
 * (non-static works fine). Won't compile (gcc 2.95.2). 
 *   -- Stephan
 * Call it like:
 *     #include 
 *     GPiece *piece = ::deserialize( mySerialTree, (GPiece *)NULL, "GPiece" );
 * If the top-level node of the tree is an object of that type, it will return a pointer
 * to the deserialized object. It returns NULL if it can't cast the object, or some other
 * error happens.
 * The class to be loaded MUST inherit from Serializable for this to work.
 */
template 
SerType *deserialize( SerialTree *tree, const SerType *ptrToType, const char *classNameInSerialTree )
{ // deprecated
    if ( ! tree ) return NULL;
    SerType *gp;
	Serializable *sp = tree->getSerializable(classNameInSerialTree);
	if ( sp == NULL ) return NULL;
    gp = dynamic_cast(sp);
    return gp;
}

/**
* Another form of deserialize() which takes an XML string instead of a SerialTree.
* use like this: Foo *foo = ::deserialize( xml_string, (Foo *)NULL, "Foo" );
* The last argument, a string, must be the base class of the node. For libGCom,
* this is always "GCom".
* todo: clsPtr should be const.
*/
template 
Type * deserialize( const QString &xml, Type *clsPtr, const QString &className )
{ // deprecated
    SerialTree *tree = SerialTree::loadFromXMLString( xml );
    if( ! tree ) return NULL;
    Type *obj = 0;
    //obj = dynamic_cast(tree->getSerializable( className ));
    obj = ::deserialize( tree, clsPtr, className );
    delete( tree );
    return obj;

}


        // quasi-experimental (stephan). The global-namespace versions are deprecated.
        /**
           SerType class to be loaded MUST inherit from Serializable for these templates
           to work.
        */

        template 
        SerType *deserialize( SerialTree *tree, const char *classNameInSerialTree );
        template 
        SerType * deserialize( const QString &xml, const QString &className );


//  Turn this on to have keys & serializable class names be stored
//  internally as ints with a lookup table instead of as QStrings.  I did
//  this because gprof was showing a lot of time spent in the QString
//  destructor, but this didn't make things faster overall.  There might be
//  some point where this is useful, like for saving to binary or over a
//  network connection.  (Loading from binary version 2, which uses this,
//  *is* faster.)
#define BOZO_CONTEXT 1

//  Turn this on to have fundamental types stored natively instead of as
//  QStrings. This sort of thing should only make a difference during
//  clone(), because when we're deserializing from a text or XML file,
//  everything is a string. (Well, the other place it should make a
//  difference--and the reason I'm trying it--is in serializing to binary. 
//  I want to see if that's faster during deserialization.)
//
//  Other effects of turning this on:
//  - larger child nodes, but that might not result in more memory use,
//    depending on what you put into it (because an int or float takes less
//    than a QString representation of an int or float)
//  - the virtual get() & put() methods which take fundamental types are
//    reimplemented (since the base class versions just convert the values
//    to QStrings)
//
//  DANGER!  If you write SerialTrees to binary files while this is on,
//  and then turn it off, you won't be able to read those files anymore!
//  (Or, put another way, people who have this turned on won't be able to
//  share binary files with people who have it turned off.)  My fault; I
//  should have not checked this in, or handled that properly in the binary
//  file support.
#define BOZO_NATIVE 1

#if BOZO_CONTEXT
class STContext;  //  internal class
#endif

/**
*  SerialTree is a simple Serializer and Deserializer which currently can
*  be saved to & loaded from XML if your Qt has XML support.  It should also
*  have saveToTextFile and loadFromTextFile methods, but at the moment,
*  it doesn't.
*
*  I would probably be using STL if I had a larger brain.
*/
class SerialTree : public Serializer, public Deserializer
{
	public:
    friend std::ostream & operator << (std::ostream&, SerialTree &);

	/**
	*  Creates an empty SerialTree node.
	*/
	SerialTree();
	virtual ~SerialTree();

	/**
	*  Searches the given node for a child element with the given key,
	*  and places its contents in rv.  If no child with the given
	*  key is found, the given default value is used.
	*/
	void get(const QString &key, QString &rv, const QString &defaultVal = QString()) const;

	/**
	*  This is just like the other getSerializable() method, except that it also
	*  takes a default class name to be used if the child element doesn't
	*  specify a class.  This is just so that files which have dozens of
	*  objects all of the same class don't need to specify the class in
	*  every element.
	*/
	void get(const QString &key, Serializable *&rv, const QString &dfltClassName, Serializable *dflt = NULL) const;


	/**
	*  See Deserializer::get(const QString &, std::vector &,
	*  const QString &) for copious notes on this topic.
	*/
	void get(const QString &key, std::vector &list, const QString &dfltClassName = QString()) const;

	void get(const QString &key, QStringList &list) const;

	/**
	*  Creates and appends a new child node containing the given string value.
	*/
	void put(const QString &key, const QString &val);
	/**
	*  This causes the given Serializable to store itself into a new node, which
	*  is appended as a child to the given node.
	*/
	void put(const QString &key, const Serializable *val);

#if BOZO_NATIVE
	void put(const QString &key, int val);
	void put(const QString &key, unsigned val);
	void put(const QString &key, bool val);
	void put(const QString &key, float val);
	void put(const QString &key, double val);
	void get(const QString &key, int &rv, int defaultVal = 0) const;
	void get(const QString &key, unsigned &rv, unsigned defaultVal = 0) const;
	void get(const QString &key, bool &rv, bool defaultVal = false) const;
	void get(const QString &key, float &rv, float defaultVal = 0.0) const;
	void get(const QString &key, double &rv, double defaultVal = 0.0) const;
#endif  //  BOZO_NATIVE

#if REIMPLEMENT_INHERITED_TEMPLATES
	//  Ha ha ha.  If you try to make these "inline", gcc 3.2 says
	//  "declaration does not declare anything".  Good!  I don't think I
	//  should have to declare these anyway!
	/**
	*  Just a wrapper around the version inherited from Deserializer.  See
	*  test/why.cpp for a lengthier complaint.
	*/
	template 
	void get(const QString &key, SC *&rv, const QString &dfltClassName=QString()) const
		{ Deserializer::get(key, rv, dfltClassName); }
	/**
	*  Just a wrapper around the version inherited from Deserializer.  See
	*  test/why.cpp for a lengthier complaint.
	*/
	template 
	void get(const QString &key, QPtrList &list, const QString &dfltClassName = QString()) const
		{ Deserializer::get(key, list, dfltClassName); }
	/**
	*  Just a wrapper around the version inherited from Deserializer.  See
	*  test/why.cpp for a lengthier complaint.
	*/
	template 
	void get(const QString &key, SC **&list, unsigned &len, const QString &dfltClassName = QString()) const
		{ Deserializer::get(key, list, len, dfltClassName); }
#endif  //  REIMPLEMENT_INHERITED_TEMPLATES

	/**
	*  This sets the name of the Serializable subclass name, so that the same
	*  class can be instantiated from this node later.
	*/
	void setSerializableClass(const char *name);

	/**
	*  This returns the name of the class which stored itself to the given
	*  node, if any.
	*/
	const char *getSerializableClass() const;

	/**
	*  Print debugging information about the structure & contents of a node
	*  and all of its children to cout.
	*/
	void dumpTree(QString indent = "") const;

	/**
	*  Opens the given filename and attempts to load its contents into a
	*  SerialTree.  If successful, it returns the new tree, which the caller
	*  is responsible for deleting.  Otherwise, it returns NULL, and you
	*  must consult steaming goat entrails to ascertain the reason.
	*
	*  (Basically this attempts to determine whether the file contains XML
	*  or plain text, and then calls loadFromXMLFile() or loadFromTextFile().)
	*/
	static SerialTree *loadFromFile(const QString &filename);

	/**
	*  Opens the given filename and attempts to load its contents into a
	*  SerialTree.  If successful, it tries to retrieve a Serializable of
	*  the given class from the given node.  If successful, it sets rv to
	*  the new Serializable, which the caller is responsible for deleting.
	*  Otherwise, it returns NULL, and you must consult steaming goat
	*  entrails to ascertain the reason.
	*/
	template 
	static void loadFromFile(const QString &filename, SC *&rv, const QString &node = "wrapper");

	/**
	*  Opens the given filename and attempts to load its contents into a
	*  SerialTree.  If successful, it returns the new tree, which the caller
	*  is responsible for deleting.  Otherwise, it returns NULL, and you
	*  must consult steaming goat entrails to ascertain the reason.
	*/
	static SerialTree *loadFromXMLFile(const QString &filename);

  /**
   * Just like loadFromXLMFile() except that it takes a QDomDocument.
   */
    static SerialTree *loadFromDomDoc( QDomDocument &xdoc );

  /**
   * Just like loadFromXLMFile() except that it takes a QString which 
   * contains XML code.
   */
    static SerialTree *loadFromXMLString( const QString &xmlstuff );


	/**
	*  Opens the given filename and attempts to save the tree as XML.
	*  If you want to append to the file instead of stomping its contents,
	*  the optional mode argument should be IO_Append (defined in qiodevice.h).
	*  NOTE that's being ignored right now.
	*/
	bool saveToXMLFile(const QString &filename, int mode = 0, int gzipLevel = 0);
	/**
	* Writes XML data to the given ostream. Returns false if the XML data is 0 bytes, else true.
	*/
    bool saveXMLToStream(std::ostream &os);


	/**
	*  Attempts to read a SerialTree as text from the given input stream.
	*  If successful, it returns the new tree, which the caller
	*  is responsible for deleting.  Otherwise, it returns NULL, and you
	*  must consult steaming goat entrails to ascertain the reason.
	*/
	static SerialTree *loadFromTextStream(QTextStream &is);
	/**
	*  Save the tree as text to the given output stream.
	*/
	bool saveToTextStream(std::ostream &os);
	/**
	 * Returns an XML string.
	 */
	QString toString();
	bool saveToTextStream( QTextStream &os, int indent=2);

  static SerialTree *decode( QDropEvent *event );

	/**
	*  Opens the given filename and attempts to load its contents into a
	*  SerialTree.  If successful, it returns the new tree, which the caller
	*  is responsible for deleting.  Otherwise, it returns NULL, and you
	*  must consult steaming goat entrails to ascertain the reason.
	*/
	static SerialTree *loadFromTextFile(const QString &filename);
	/**
	*  Opens the given filename and attempts to save the tree as text.
	*  If you want to append to the file instead of stomping its contents,
	*  the optional mode argument should be IO_Append (defined in qiodevice.h).
	*  NOTE that's being ignored right now.
	*/
	bool saveToTextFile(const QString &filename, int mode = 0);


	/**
	*  Attempts to read a SerialTree in binary from the given input stream.
	*  If successful, it returns the new tree, which the caller
	*  is responsible for deleting.  Otherwise, it returns NULL, and you
	*  must consult steaming goat entrails to ascertain the reason.
	*/
	static SerialTree *loadFromBinaryStream(QDataStream &is);
	/**
	*  Save the tree as binary to the given output stream.
	*
	*  The version number is 1 for version 1, 2 for version 2, or 0 for the
	*  default (which depends on whether BOZO_CONTEXT is set in SerialTree.h).
	*/
	bool saveToBinaryStream(QDataStream &os, int version = 0);
	/**
	*  Opens the given filename and attempts to load its contents into a
	*  SerialTree.  If successful, it returns the new tree, which the caller
	*  is responsible for deleting.  Otherwise, it returns NULL, and you
	*  must consult steaming goat entrails to ascertain the reason.
	*/
	static SerialTree *loadFromBinaryFile(const QString &filename);
	/**
	*  Opens the given filename and attempts to save the tree as binary.
	*  If you want to append to the file instead of stomping its contents,
	*  the optional mode argument should be IO_Append (defined in qiodevice.h).
	*  NOTE that's being ignored right now.
	*
	*  The version number is 1 for version 1, 2 for version 2, or 0 for the
	*  default (which depends on whether BOZO_CONTEXT is set in SerialTree.h).
	*/
	bool saveToBinaryFile(const QString &filename, int verson = 0, int mode = 0);


	/**
	*  Copies the object by serializing & deserializing it.  (Maybe
	*  not the most efficient way, but it's easy...)
	*
	*  If a Deserialization::Context is given, it will be passed to
	*  pushContext() before deserialization, and deleted afterwards.
	*/
	template 
	static SC *clone(const SC &rv, Deserializer::Context *context = NULL);

	private:
	Serializable *toSerializable(const QString & dfltClassName) const;
	
//	protected:
//	/**
//	*  Subclasses might want to override this to create children of their own
//	*  class.
//	*/
//	virtual SerialTree *addChild(const QString &key);


	private:
	struct Child
	{
		~Child();
#if BOZO_CONTEXT
		//  The reason for the hideously ugly code (instead of using the
		//  same variable name, which would have meant less #ifdefs in the
		//  source files) is so that the compiler forces me to check every
		//  place where they're used.
		unsigned ikey;
#else
		QString skey;
#endif  //  BOZO_CONTEXT

		enum ChildType
		{
			CT_TREE,
			CT_QSTRING
#if BOZO_NATIVE
			, CT_INT,
			CT_UNSIGNED,
			CT_BOOL,
			CT_FLOAT,
			CT_DOUBLE
#endif  //  BOZO_NATIVE
		} ct;
		union VT
		{
			inline VT(QString *sv)    : s(sv) { }
			inline VT(SerialTree *tv) : t(tv) { }
#if BOZO_NATIVE
			inline VT(int iv)         : i(iv) { }
			inline VT(unsigned uv)    : u(uv) { }
			inline VT(bool bv)        : b(bv) { }
			inline VT(float fv)       : f(fv) { }
			inline VT(double dv)      : d(dv) { }
#endif  //  BOZO_NATIVE
			QString *s;
			SerialTree *t;
#if BOZO_NATIVE
			int i;
			unsigned u;
			bool b;
			float f;
			double d;  //  nuts, that bloats this up to 8 bytes.  You could
			           //  make that pointer-to-double if you don't expect
			           //  to use them much...
#endif  //  BOZO_NATIVE
		} val;

#if BOZO_NATIVE
		QString stringVal() const;
#endif  //  BOZO_NATIVE

#if BOZO_CONTEXT
		inline Child(unsigned k, QString *s)    : ikey(k), ct(CT_QSTRING),  val(s) { }
		inline Child(unsigned k, SerialTree *t) : ikey(k), ct(CT_TREE),     val(t) { }
#if BOZO_NATIVE
		inline Child(unsigned k, int i)         : ikey(k), ct(CT_INT),      val(i) { }
		inline Child(unsigned k, unsigned u)    : ikey(k), ct(CT_UNSIGNED), val(u) { }
		inline Child(unsigned k, bool b)        : ikey(k), ct(CT_BOOL),     val(b) { }
		inline Child(unsigned k, float f)       : ikey(k), ct(CT_FLOAT),    val(f) { }
		inline Child(unsigned k, double d)      : ikey(k), ct(CT_DOUBLE),   val(d) { }
#endif  //  BOZO_NATIVE
#else
		inline Child(const QString &k, QString *s)    : skey(k), ct(CT_QSTRING),  val(s) { }
		inline Child(const QString &k, SerialTree *t) : skey(k), ct(CT_TREE),     val(t) { }
#if BOZO_NATIVE
		inline Child(const QString &k, int i)         : skey(k), ct(CT_INT),      val(i) { }
		inline Child(const QString &k, unsigned u)    : skey(k), ct(CT_UNSIGNED), val(u) { }
		inline Child(const QString &k, bool b)        : skey(k), ct(CT_BOOL),     val(b) { }
		inline Child(const QString &k, float f)       : skey(k), ct(CT_FLOAT),    val(f) { }
		inline Child(const QString &k, double d)      : skey(k), ct(CT_DOUBLE),   val(d) { }
#endif  //  BOZO_NATIVE
#endif  //  BOZO_CONTEXT
	};
	/**
NOT TRUE
	*  Returns the first child with the given key. If no such child exists,
	*  then getChild(key).isNull() will be false.  If you simply want to
	*  determine whether such a child exists, use hasChild(key).
	*/
//	const SerialTree &getChild(const QString &key) const;
	const Child *getChild(const QString &key) const;
	std::vector children;
#if BOZO_CONTEXT
	int serialClassID;
#else
	char *serialClass;
#endif  //  BOZO_CONTEXT

#if BOZO_CONTEXT
	SerialTree(STContext *);
	STContext *context;
#endif  //  BOZO_CONTEXT

	//  internal XML support
	void toXMLNode(QDomNode &xmlnode) const;
	static void childToXMLNode(const Child &child, QDomNode &xmlnode);
#if BOZO_CONTEXT
	static Child *childFromXMLNode(STContext *, const QString &key, const QDomElement &xmlnode);
#else
	static Child *childFromXMLNode(const QString &key, const QDomElement &xmlnode);
#endif  //  BOZO_CONTEXT

	//  internal text support
	bool childrenToText(std::ostream &os);
	void childrenFromText(QString &buf, int &bufpos, QTextStream &is);

	//  internal binary support
	bool childrenToBinary1(QDataStream &os);
	void childrenFromBinary1(QDataStream &is);
#if BOZO_CONTEXT
	bool childrenToBinary2(QDataStream &os);
	void childrenFromBinary2(QDataStream &is);
	void contextToBinary2(QDataStream &os);
	void contextFromBinary2(QDataStream &is);
#endif  //  BOZO_CONTEXT

//     // experiment by stephan
// public:
//     const QString &name() const { return this->m_name; };
//     void setName( const QString &n ) { this->m_name = n; };


//         /**
//            operator >> added by stephan, and is completely untested.
//         */
//         friend SerialTree & operator <<( SerialTree &lhs, const Serializable &rhs )
//         {
// //             QString n = (n = lhs.name()).isEmpty() ? QString("SerialTree") : n;
// //             cerr << "SerialTree << " << n.data() << endl;
// //             lhs.putSerializable( n.isEmpty() ? QString("SerialTree") : n, rhs );
//             return lhs;
//         };



//         /**
//            operator >> added by stephan, and is completely untested.
//         */
//         const SerialTree & operator >>( Serializable &rhs )
//         {
//             QString n = (n = this->name()).isEmpty() ? QString("SerialTree") : n;
//             cerr << "SerialTree >> " << n.data() << endl;
// //             Serializable *sp = this->getSerializable( );
// // 	if ( sp == NULL ) return NULL;
// //     gp = dynamic_cast(sp);
// //             this->deserialize( rhs );
//             return *this;
//         };
// private: 
//     QString m_name;

};

template 
void
SerialTree::loadFromFile(const QString &filename, SC *&rv, const QString &node)
{
	rv = NULL;
	Deserializer *ds = loadFromFile(filename);
	if (!ds) return;
	ds->get(node, rv);
	delete ds;
}

template 
SC *
SerialTree::clone(const SC &src, Deserializer::Context *cp)
{
	SC *rv = NULL;
	SerialTree st;
	Deserializer *dp = &st;  // arghh, see tests/why.cpp
	st.put("clone", &src);
	if (cp) dp->pushContext(cp);
	dp->get("clone", rv);
	if (cp) dp->popContext();
	return rv;
}

}  //  namespace fun

#if BOZO_CONTEXT
#include 
#include 
namespace fun {
class STContext
{
	public:
	STContext();
	~STContext();
	unsigned getKey(const QString &key);
	unsigned getClass(const char *key);
	const QString &getKeyString(unsigned id) { return *(keylut[id]); }
	const char *getClassString(unsigned id) { return classlut[id]; }
	int refcount;
	QDict keymap;
	QAsciiDict classmap;
	std::vector keylut;
	std::vector classlut;
};
}  //  namespace fun
#endif  //  BOZO_CONTEXT

#endif  //  _FUN_SERIALTREE_H

Generated by: stephan on cheyenne on Mon Aug 11 14:06:52 2003, using kdoc 2.0a54.