QObject XML Model Example

This example shows how to use QtXmlPatterns to query QObject trees by modeling the non-XML data structure of a QObject tree to look like XML.

Introduction

This example illustrates two important points about using XQuery to query non-XML data modeled to look like XML. The first point is that a custom node model class doesn't always have to actually build the node model. Sometimes the node model can be an already existing data structure, like the QObject tree used in this example. The second point is to explain what is required to make non-XML data look like XML.

In this example, we want to model a QObject tree to look like XML. That is easy to do because a QObject tree maps to the XML tree structure in a staightforward way. Each QObject node is modeled as an XML element node. However, when we want to add the QMetaObject tree to the QObject tree node model, we are trying to add a second tree to the node model. The QMetaObject tree exists behind the QObject tree. Adding the QMetaObject tree to the node model changes the two dimensional tree into a three dimensional tree.

The query engine can only traverse two dimensional trees, because an XML document is always a two dimensional tree. If we want to add the QMetaObject tree to the node model, we have to somehow flatten it into the same plane as the QObject tree. This requires that the node model class must build an auxiliary data structure and make it part of the two dimensional QObject node model. How to do this is explained in Including The QMetaObject Tree.

The User Interface

The UI for this example was created using Qt Designer:

Code Walk-Through

The strategy for this example is different from the strategy for the file system example. In the file system example, the node model class had to actually build a node model because the non-XML data to be traversed was the computer's file system, a structure stored on disk in a form that the query engine couldn't use. The node model class had to build an analog of the computer's file system in memory.

For this example, the data structure to be traversed already exists in memory in a usable form. It is the QObject tree of the example application itself. All we need is the pointer to the root of the QObject tree.

Note: When we add the QMetaObject tree to the node model, the node model class will have to build an auxiliary data structure to move the QMetaObject tree into the same plane as the QObject tree. This is explained later in Including The QMetaObject Tree.

The Custom Node Model Class: QObjextXmlModel

The node model class for this example is QObjextXmlModel, which is derived from QSimpleXmlNodeModel. QObjextXmlModel implements the callback interface functions that don't have implementations in QSimpleXmlNodeModel:

    virtual QXmlNodeModelIndex::DocumentOrder compareOrder(const QXmlNodeModelIndex &n1, const QXmlNodeModelIndex &n2) const;
    virtual QXmlName name(const QXmlNodeModelIndex &n) const;
    virtual QUrl documentUri(const QXmlNodeModelIndex &n) const;
    virtual QXmlNodeModelIndex::NodeKind kind(const QXmlNodeModelIndex &n) const;
    virtual QXmlNodeModelIndex root(const QXmlNodeModelIndex &n) const;
    virtual QVariant typedValue(const QXmlNodeModelIndex &n) const;
    virtual QVector<QXmlNodeModelIndex> attributes(const QXmlNodeModelIndex&) const;
    virtual QXmlNodeModelIndex nextFromSimpleAxis(SimpleAxis, const QXmlNodeModelIndex&) const;

The node model class declares three data members:

    const QUrl              m_baseURI;
    QObject *const          m_root;
    const AllMetaObjects    m_allMetaObjects;

The constructor sets m_baseURI to the QUrl constructed from the file path of the application executable. This is the value returned by documentUri(). The constructor sets m_root to point to the QObject tree for the example application. This is the node model that the query engine will use. And the constructor calls a local function to build the auxiliary data structure (m_allMetaObjects) for including the QMetaObject tree in the node model. How this auxiliary data structure is incorporated into the QObject node model is discussed in Including The QMetaObject Tree.

Accessing The Node Model

Since the query engine knows nothing about QObject trees, it can only access them by calling functions in the node model callback interface. The query engine passes a QXmlNodeModelIndex to uniquely identify a node in the node model. The QXmlNodeModelIndex is constructed from a pointer to the QObject that represents the node. createIndex() creates the QXmlNodeModelIndex, as in the local root() function, for example:

QXmlNodeModelIndex QObjectXmlModel::root() const
{
    return createIndex(m_root);
}

A QObject represents an element node in the node model, but we also need to represent attribute nodes. For example, the class name of a QObject is an attribute of the QObject, so it should be an attribute node in the node model. A QObject's class name is obtained from the QObject. (Actually, it is in the QMetaObject, which is obtained from the QObject). This means that a single QObject logically represents multiple nodes in the node model: the element node and potentially many attribute nodes.

To uniquely identify an attribute node, we need the pointer to the QObject containing the attribute, and an additional value that identifies the attribute in the QObject. For this additional data value, we use enum QObjectNodeType:

    enum QObjectNodeType
    {
        IsQObject               = 0,
        QObjectProperty         = 1 << 26,
        MetaObjects             = 2 << 26,
        MetaObject              = 3 << 26,
        MetaObjectClassName     = 4 << 26,
        MetaObjectSuperClass    = 5 << 26,
        QObjectClassName        = 6 << 26
    };

Ignore the MetaObjectXXX values for now. They will be explained in Including The QMetaObject Tree. Here we are interested in the three node types for QObject nodes: IsQObject, which represents the element node type for a QObject, and QObjectProperty and QObjectClassName, which represent the attribute node types for the attributes of a QObject.

The createIndex() function called in the root() snippet above is the overload that accepts a void* pointer and a second parameter, additionalData, with default value 0 (IsQObject). Wherever you see a call to createIndex() that only passes the QObject pointer, it is creating the node index for a QObject element node. To create the node index for the class name attribute, for example, the attributes() function uses createIndex(object,QObjectClassName).

QVector<QXmlNodeModelIndex> QObjectXmlModel::attributes(const QXmlNodeModelIndex& n) const
{
     QVector<QXmlNodeModelIndex> result;
     QObject *const object = asQObject(n);

     switch(toNodeType(n))
     {
        case IsQObject:
        {
            const QMetaObject *const metaObject = object->metaObject();
            const int count = metaObject->propertyCount();
            result.append(createIndex(object, QObjectClassName));

            for (int i = 0; i < count; ++i) {
                const QMetaProperty qmp(metaObject->property(i));
                const int ii = metaObject->indexOfProperty(qmp.name());
                if (i == ii)
                    result.append(createIndex(object, QObjectProperty | i));
            }
            return result;
        }
        default:
            return QVector<QXmlNodeModelIndex>();
     }
}

attributes() is one of the callback functions you have to implement in your custom node model class. It returns a QVector of node indexes for all the attribute nodes for QObject n. It calls createIndex() in two places. Both calls use the QObject pointer from the current node n (the element node), and just add a different value for the additional data parameter. This makes sense because, in XML, the attributes of an element are part of that element.

Traversing The Node Model

The query engine traverses the QObject tree by calling back to the node model class's implementation of nextFromSimpleAxis(). This function is the heart of the callback interface, and it will probably be the most complex to implement in your custom node model class. Below is a partial listing of the implementation for this example. The full listing will be shown in Including The QMetaObject Tree, where we discuss traversing the QMetaObject tree.

QXmlNodeModelIndex QObjectXmlModel::nextFromSimpleAxis(SimpleAxis axis, const QXmlNodeModelIndex &n) const
{
    switch (toNodeType(n))
    {
        case IsQObject:
        {
            switch (axis)
            {
                case Parent:
                    return createIndex(asQObject(n)->parent());

                case FirstChild:
                {
                    if (!asQObject(n) || asQObject(n)->children().isEmpty())
                        return QXmlNodeModelIndex();
                    else
                        return createIndex(asQObject(n)->children().first());
                }

                case NextSibling:
                    return qObjectSibling(1, n);

                case PreviousSibling:
                {
                    if (asQObject(n) == m_root)
                        return createIndex(qint64(0), MetaObjects);
                    else
                        return qObjectSibling(-1, n);
                }
            }
            Q_ASSERT(false);
        }

        case QObjectClassName:
        case QObjectProperty:
        {
            Q_ASSERT(axis == Parent);
            return createIndex(asQObject(n));
        }
    }

    Q_ASSERT(false);
    return QXmlNodeModelIndex();
}

The main switch uses toNodeType(), which obtains the node type from QXmlNodeModelIndex::additionalData():

QObjectXmlModel::QObjectNodeType QObjectXmlModel::toNodeType(const QXmlNodeModelIndex &n)
{
    return QObjectNodeType(n.additionalData() & (15 << 26));
}

case IsObject case is the most interesting. It switches again on the value of the axis parameter, which specifies the direction the query engine wants to take from the current node. It is one of the four enum values of QAbstractXmlNodeModel::SimpleAxis. The Parent and FirstChild cases reduce to calls to QObject::parent() and QObject::children() respectively. Note that a default constructed QXmlNodeModelIndex is returned in the Parent case if the current node is the root, and in the FirstChild case if the current node has no children.

For the NextSibling and PreviousSibling axes, the helper function qObjectSibling() is called, with +1 to traverse to the NextSibling and -1 to traverse to the PreviousSibling.

QXmlNodeModelIndex QObjectXmlModel::qObjectSibling(const int pos, const QXmlNodeModelIndex &n) const
{
    Q_ASSERT(pos == 1 || pos == -1);
    Q_ASSERT(asQObject(n));

    const QObject *parent = asQObject(n)->parent();
    if (parent) {
        const QList<QObject *> &children = parent->children();
        const int siblingPos = children.indexOf(asQObject(n)) + pos;

        if (siblingPos >= 0 && siblingPos < children.count())
            return createIndex(children.at(siblingPos));
        else
            return QXmlNodeModelIndex();
    }
    else
        return QXmlNodeModelIndex();
}

qObjectSibling() determines whether or not the node has any siblings. It is called with n, the index of the current node. If the current node is a child, then it has a parent with children (the current node one of these). So, we get the parent, obtain the parent's child list, find the current node in the list, and construct the node index for the next or previous child (sibling) and return it.

Note: In nextFromSimpleAxis(), the special case of asking for the PreviousSibling of the root node is discussed in Including The QMetaObject Tree.

Traversing away from a QObjectClassName attribute node or a QObjectProperty attribute node might seem a bit confusing at first glance. The only move allowed from an attribute node is to the Parent, because attribute nodes don't have children. But these two cases simply return the node index of the current node.

        case QObjectClassName:
        case QObjectProperty:
        {
            Q_ASSERT(axis == Parent);
            return createIndex(asQObject(n));
        }

Since n is the QXmlNodeModelIndex of the current node, all this does is create another QXmlNodeModelIndex for the current node and return it. This was explained above in Accessing The Node Model, where we saw that each QObject in the node model actually represents an element node and potentially many attribute nodes. Traversing to the parent node of an attribute simply creates a node index for the same QObject, but with an additional data value of 0 (IsQObject).

If we only wanted to traverse the QObject tree with XQuery, we could just implement the rest of the virtual callback functions listed earlier and we would be done. The implementations for the remaining functions are straightforward. But if we also want to use XQuery to traverse the QMetaObject tree, we must include the QMetaObject tree in the custom node model.

Including The QMetaObject Tree

The metaobject system not only enables Qt's signals and slots, it also provides type information that is useful at run-time; e.g., getting and setting properties without knowing the property names at compile time. Each QObject has an associated QMetaObject tree which contains all this useful type information. Given a QObject, its QMetaObject is obtained with QObject::metaObject(). Then QMetaObject::superClass() can be called repeatedly to get the QMetaObject for each class in the class hierarchy for the original QObject.

However, the QMetaObject hierarchy is a second tree in a plan that exists logically behind the plane of the QObject tree. The QtXmlPatterns query engine can only traverse a two dimensional node model that represents an XML tree. If we want to include the QMetaObject in the same node model that represents the QObject tree, we must find a way to flatten the QMetaObject tree into the same plane as the QObject tree.

The node model class declares m_allMetaObjects as a vector of pointers to QMetaObject:

    typedef QVector<const QMetaObject *> AllMetaObjects;
    const AllMetaObjects    m_allMetaObjects;

This vector gets populated by the QObjectXmlModel constructor by calling the private allMetaObjects() function:

QObjectXmlModel::AllMetaObjects QObjectXmlModel::allMetaObjects() const
{
    QXmlQuery query(namePool());
    query.bindVariable("root", root());
    query.setQuery("declare variable $root external;"
                   "$root/descendant-or-self::QObject");
    Q_ASSERT(query.isValid());

    QXmlResultItems result;
    query.evaluateTo(&result);
    QXmlItem i(result.next());

    AllMetaObjects objects;
    while (!i.isNull()) {
        const QMetaObject *moo = asQObject(i.toNodeModelIndex())->metaObject();
        while (moo) {
            if (!objects.contains(moo))
                objects.append(moo);
            moo = moo->superClass();
        }
        i = result.next();
    }

    Q_ASSERT(!objects.contains(0));
    return objects;
}

The first half of the function is an example of the standard code pattern for using QtXmlPatterns to run an XQuery. First it creates an instance of QXmlQuery. Then it binds the XQuery variable $root to the root node of the of the node model; i.e., the root of the QObject tree. Then it sets the query to be an XQuery that returns all the QObjects in the node model. Finally, the query is evaluated into a result item list.

Note: QXmlQuery::bindVariable() must be called before QXmlQuery::setQuery(), because setting the query causes QtXmlPatterns to compile the XQuery, which requires knowledge of the variable bindings.

The second half of the function traverses the result item list, getting the QMetaObject hierarchy for each QObject and appending it to m_allMetaObjects, if it isn't already there. But how do we include this vector of pointers to QMetaObjects in the node model? The key insight is shown in the full listing of nextFromSimpleAxis(), where we are interested now in the MetaObjectXXX cases:

QXmlNodeModelIndex QObjectXmlModel::nextFromSimpleAxis(SimpleAxis axis, const QXmlNodeModelIndex &n) const
{
    switch (toNodeType(n))
    {
        case IsQObject:
        {
            switch (axis)
            {
                case Parent:
                    return createIndex(asQObject(n)->parent());

                case FirstChild:
                {
                    if (!asQObject(n) || asQObject(n)->children().isEmpty())
                        return QXmlNodeModelIndex();
                    else
                        return createIndex(asQObject(n)->children().first());
                }

                case NextSibling:
                    return qObjectSibling(1, n);

                case PreviousSibling:
                {
                    if (asQObject(n) == m_root)
                        return createIndex(qint64(0), MetaObjects);
                    else
                        return qObjectSibling(-1, n);
                }
            }
            Q_ASSERT(false);
        }

        case QObjectClassName:
        case QObjectProperty:
        {
            Q_ASSERT(axis == Parent);
            return createIndex(asQObject(n));
        }
        case MetaObjects:
        {
            switch (axis)
            {
                case Parent:
                    return QXmlNodeModelIndex();
                case PreviousSibling:
                    return QXmlNodeModelIndex();
                case NextSibling:
                    return root();
                case FirstChild:
                {
                    return createIndex(const_cast<QMetaObject*>(m_allMetaObjects.first()),MetaObject);
                }
            }
            Q_ASSERT(false);
        }

        case MetaObject:
        {
            switch (axis)
            {
                case FirstChild:
                    return QXmlNodeModelIndex();
                case Parent:
                    return createIndex(qint64(0), MetaObjects);
                case PreviousSibling:
                    return metaObjectSibling(-1, n);
                case NextSibling:
                    return metaObjectSibling(1, n);
            }
        }

        case MetaObjectClassName:
        case MetaObjectSuperClass:
        {
            Q_ASSERT(axis == Parent);
            return createIndex(asQObject(n), MetaObject);
        }
    }

    Q_ASSERT(false);
    return QXmlNodeModelIndex();
}

But first, revisit the PreviousSibling case for the IsQObject case:

                case PreviousSibling:
                {
                    if (asQObject(n) == m_root)
                        return createIndex(qint64(0), MetaObjects);
                    else
                        return qObjectSibling(-1, n);
                }

When asking for the previous sibling of the root of the QObject tree, it creates a node model index with a null QObject pointer and an additionalData value of MetaObjects. This effectively allows the query engine to jump from the QObject tree to the QMetaObject tree.

The query engine can jump from the QMetaObject tree back to the QObject tree in the NextSibling case of case MetaObjects, where the root() function is called:

        case MetaObjects:
        {
            switch (axis)
            {
                case Parent:
                    return QXmlNodeModelIndex();
                case PreviousSibling:
                    return QXmlNodeModelIndex();
                case NextSibling:
                    return root();
                case FirstChild:
                {
                    return createIndex(const_cast<QMetaObject*>(m_allMetaObjects.first()),MetaObject);
                }
            }
            Q_ASSERT(false);
        }

Having jumped from the QObject tree to the QMetaObject tree, the query engine will use the MetaObject, MetaObjectClassName, and MetaObjectSuperClass cases, which are similar to the cases for IsQObject, QObjectProperty, and QObjectClassName.

Files:

© 2015 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.