[ fromfile: memento.xml id: importing ]
| Prerequisites | |
|---|---|
The importing routine is a bit more sophisticated than the exporting routine, and it has a couple of interesting features.
It parses XML using the SAX parser.
Depending on the input, it creates objects.
The number and types of objects, as well as their parent-child relationships, must be reconstructed from the information in the file.
Example 17.19 shows the class definition for QObjectReader.
Example 17.19. src/libs/dataobjects/qobjectreader.h
[ . . . . ] #include "dobjs_export.h" #include <QString> #include <QStack> #include <QQueue> #include <QXmlDefaultHandler> class AbstractFactory; class DOBJS_EXPORT QObjectReader : public QXmlDefaultHandler { public: QObjectReader (AbstractFactory* factory=0) : m_Factory(factory), m_Current(0) { } QObjectReader (QString filename, AbstractFactory* factory=0); void parse(QString text); void parseFile(QString filename); QObject* getRoot(); ~QObjectReader(); // callback methods from QXmlDefaultHandler bool startElement( const QString & namespaceURI, const QString & name, const QString & qualifiedName, const QXmlAttributes & attributes ); bool endElement( const QString & namespaceURI, const QString & localName, const QString & qualifiedName); bool endDocument(); private: void addCurrentToQueue(); AbstractFactory* m_Factory; QObject* m_Current; QQueue<QObject*> m_ObjectList; QStack<QObject*> m_ParentStack; }; [ . . . . ]
Figure 17.2 shows the relationships between the various classes that we will be using.
QObjectReader is derived from QXmlDefaultHandler,
which is a plugin for the QXmlSimpleReader.
AbstractFactory is a plugin for QObjectReader.
When we create a QObjectReader, we must supply it with a concrete class, such as ObjectFactory or DataObjectFactory.
QObjectReader is now completely separate from the specific types of objects that it can create. To use it with your own types, just derive a factory from AbstractFactory for them.
Think about Example 17.16 as you read the code that constructs objects from it in the code below.
startElement() is called when the SAX parser encounters the initial tag of an XML element.
As we see in Example 17.20, the parameters to this function contain all the information we need to create an object.
All other objects that are encountered between startElement() and the matching endElement() are children of m_Current.
Example 17.20. src/libs/dataobjects/qobjectreader.cpp
[ . . . . ] bool QObjectReader::startElement( const QString &, const QString & elementName, const QString &, const QXmlAttributes & atts) {if (elementName == "object") { if (m_Current != 0)
m_ParentStack.push(m_Current);
QString classname = atts.value("class"); QString instancename = atts.value("name"); if (m_Factory ==0) { m_Current = ObjectFactory::instance()->newObject(classname); } else { m_Current=m_Factory->newObject(classname); } m_Current->setObjectName(instancename); if (!m_ParentStack.empty()) {
m_Current->setParent(m_ParentStack.top()); } return true; } if (elementName == "property") { QString fieldType = atts.value("type"); QString fieldName = atts.value("name"); QString fieldValue = atts.value("value"); QVariant qv = variantFrom(fieldType, fieldValue); bool ok = m_Current->setProperty(fieldName.toAscii(), qv); if (!ok) { } } return true; }
| Unnamed parameters are a way of avoiding “parameter not used” warnings from the compiler. It is necessary to include the parameters, even though we do not need them for this application, so that the signature matches that of the base class method and polymorphic overrides will be properly called. | |
| If we are already inside an <object> | |
| Keep track of the current parent | |
| If this element has a parent, it is on the top of the stack. Set its parent. |
The object is “finished” when we reach endElement(), shown in Example 17.21.
Example 17.21. src/libs/dataobjects/qobjectreader.cpp
QObjectReader uses an Abstract Factory to do the actual object creation.
The callback function, newObject(QString className), creates an object that can hold all of the properties described in className.
ObjectFactory creates “pseudo-objects” that are not exactly the CustomerList and Customer classes, but they “mimic” them well enough that the export/import process works round-trip.
You can write a concrete factory that returns the proper types for each classname if you want the de-serialized tree to have the same types as the objects in the original tree.
Each time a new address type is added to this library, we can add another else clause to the createObject function, as shown in Example 17.22.
Example 17.22. src/libs/dataobjects/objectfactory.cpp
[ . . . . ] QObject* ObjectFactory::newObject(QString className, QObject* parent) { QObject* retval = 0; if (className == "UsAddress") { retval = newAddress(Country::USA); } else if (className == "CanadaAddress") { retval = newAddress(Country::Canada); } else { qDebug() << QString("Generic QObject created for new %1") .arg(className); retval = new QObject(); retval->setProperty("className", className); retval->setParent(parent); } return retval; }
| Generated: $Date: 2009-09-08 12:15:32 -0400 (Tue, 08 Sep 2009) $ | İ 2009 Alan Ezust and Paul Ezust. |