16.2.2. Importing Objects with an Abstract Factory

[ fromfile: memento.xml id: importing ]

[Caution] Prerequisites

The importing routine is more sophisticated than the exporting routine, and it has some interesting features.

Example 16.12 shows the class definition for QObjectReader.

Example 16.12. 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:
    explicit QObjectReader (AbstractFactory* factory=0) : 
                         m_Factory(factory), m_Current(0) { }
    explicit 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 16.3 shows the relationships between the various classes in the example.

Figure 16.3. QObjectReader and Its Related Classes

QObjectReader and Its Related Classes

QObjectReader is derived from QXmlDefaultHandler, which is a plugin for the QXmlSimpleReader. AbstractFactory is a plugin for QObjectReader. When you create a QObjectReader, you must supply it with a concrete instance of 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.

Keep in mind the XML output file in Example 16.10 as you read the code that constructs objects from it, starting with Example 16.13.

Example 16.13. src/libs/dataobjects/qobjectreader.cpp

[ . . . . ]

bool QObjectReader::startElement( const QString&,
     const QString& elementName, const QString&,
     const QXmlAttributes& atts) {              1
    if (elementName == "object") {
        if (m_Current != 0)                     2
            m_ParentStack.push(m_Current);      3
        QString classname = atts.value("class");
        QString instancename = atts.value("name");
        m_Current = m_Factory->newObject(classname);
        m_Current->setObjectName(instancename);
        if (!m_ParentStack.empty()) {           4           
            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 = QVariant(fieldValue);
        m_Current->setProperty(fieldName.toAscii(), qv);

    }
    return true;
}

1

No need to name unused parameters.

2

If already inside a <object>,

3

push previous current onto stack.

4

Top of parentstack, or previous current, should be my parent.


startElement() is called when the SAX parser encounters the initial tag of an XML element. The parameters to this function contain all the information needed to create an object. All other objects encountered between startElement() and the matching endElement() are children of m_Current. The object is "finished" when we reach endElement(), as shown in Example 16.14.

Example 16.14. src/libs/dataobjects/qobjectreader.cpp

[ . . . . ]

bool QObjectReader::endElement( const QString& ,
                            const QString& elementName,
                            const QString& ) {
    if (elementName == "object") {
        if (!m_ParentStack.empty())
            m_Current = m_ParentStack.pop();
        else {
            addCurrentToQueue();
        }
    }
    return true;
}

QObjectReader uses an Abstract Factory to do the actual object creation.

The callback function, newObject(QString className), creates an object that can hold all the properties described in className. ObjectFactory creates properly classed objects for the types for which it has a QMetaObject. For other classes, it creates a regular QObject, but tries to "mimic" the type by setting its className dynamic property. To support your own types, you can write a concrete derived factory that maps the correct strings to QMetaObject.