Simple DOM Model Example▲
Qt provides two complementary sets of classes for reading XML files: The classes based around QXmlReader provide a SAX-style API for incremental reading of large files, and the classes based around QDomDocument enable developers to access the contents of XML files using a Document Object Model (DOM) API.
In this example, we create a model that uses the DOM API to expose the structure and contents of XML documents to views via the standard QAbstractModel interface.
Design and Concepts▲
Reading an XML document with Qt's DOM classes is a straightforward process. Typically, the contents of a file are supplied to QDomDocument, and nodes are accessed using the functions provided by QDomNode and its subclasses.
The aim is to use the structure provided by QDomDocument by wrapping QDomNode objects in item objects similar to the TreeItem objects used in the Simple Tree Model example.
DomModel Class Definition▲
Let us begin by examining the DomModel class:
class
DomModel : public
QAbstractItemModel
{
Q_OBJECT
public
:
explicit
DomModel(const
QDomDocument &
amp;document, QObject *
parent =
nullptr
);
~
DomModel();
QVariant data(const
QModelIndex &
amp;index, int
role) const
override
;
Qt::
ItemFlags flags(const
QModelIndex &
amp;index) const
override
;
QVariant headerData(int
section, Qt::
Orientation orientation,
int
role =
Qt::
DisplayRole) const
override
;
QModelIndex index(int
row, int
column,
const
QModelIndex &
amp;parent =
QModelIndex()) const
override
;
QModelIndex parent(const
QModelIndex &
amp;child) const
override
;
int
rowCount(const
QModelIndex &
amp;parent =
QModelIndex()) const
override
;
int
columnCount(const
QModelIndex &
amp;parent =
QModelIndex()) const
override
;
private
:
QDomDocument domDocument;
DomItem *
rootItem;
}
;
The class definition contains all the basic functions that are needed for a read-only model. Only the constructor and document() function are specific to this model. The private domDocument variable is used to hold the document that is exposed by the model; the rootItem variable contains a pointer to the root item in the model.
DomItem Class Definition▲
The DomItem class is used to hold information about a specific QDomNode in the document:
class
DomItem
{
public
:
DomItem(const
QDomNode &
amp;node, int
row, DomItem *
parent =
nullptr
);
~
DomItem();
DomItem *
child(int
i);
DomItem *
parent();
QDomNode node() const
;
int
row() const
;
private
:
QDomNode domNode;
QHash&
lt;int
, DomItem *&
gt; childItems;
DomItem *
parentItem;
int
rowNumber;
}
;
Each DomItem provides a wrapper for a QDomNode obtained from the underlying document which contains a reference to the node, it's location in the parent node's list of child nodes, and a pointer to a parent wrapper item.
The parent(), child(), and row() functions are convenience functions for the DomModel to use that provide basic information about the item to be discovered quickly. The node() function provides access to the underlying QDomNode object.
As well as the information supplied in the constructor, the class maintains a cache of information about any child items. This is used to provide a collection of persistent item objects that the model can identify consistently and improve the performance of the model when accessing child items.
DomItem Class Implementation▲
Since the DomItem class is only a thin wrapper around QDomNode objects, with a few additional features to help improve performance and memory usage, we can provide a brief outline of the class before discussing the model itself.
The constructor simply records details of the QDomNode that needs to be wrapped:
DomItem::
DomItem(const
QDomNode &
amp;node, int
row, DomItem *
parent)
:
domNode(node),
parentItem(parent),
rowNumber(row)
{}
As a result, functions to provide the parent wrapper, the row number occupied by the item in its parent's list of children, and the underlying QDomNode for each item are straightforward to write:
DomItem *
DomItem::
parent()
{
return
parentItem;
}
int
DomItem::
row() const
{
return
rowNumber;
}
QDomNode DomItem::
node() const
{
return
domNode;
}
It is necessary to maintain a collection of items which can be consistently identified by the model. For that reason, we maintain a hash of child wrapper items that, to minimize memory usage, is initially empty. The model uses the item's child() function to help create model indexes, and this constructs wrappers for the children of the item's QDomNode, relating the row number of each child to the newly-constructed wrapper:
DomItem *
DomItem::
child(int
i)
{
DomItem *
childItem =
childItems.value(i);
if
(childItem)
return
childItem;
// if child does not yet exist, create it
if
(i &
gt;=
0
&
amp;&
amp; i &
lt; domNode.childNodes().count()) {
QDomNode childNode =
domNode.childNodes().item(i);
childItem =
new
DomItem(childNode, i, this
);
childItems[i] =
childItem;
}
return
childItem;
}
If a QDomNode was previously wrapped, the cached wrapper is returned; otherwise, a new wrapper is constructed and stored for valid children, and zero is returned for invalid ones.
The class's destructor deletes all the child items of the wrapper:
DomItem::
~
DomItem()
{
qDeleteAll(childItems);
}
These, in turn, will delete their children and free any QDomNode objects in use.
DomModel Class Implementation▲
The structure provided by the DomItem class makes the implementation of DomModel similar to the TreeModel shown in the Simple Tree Model example.
The constructor accepts an existing document and a parent object for the model:
DomModel::
DomModel(const
QDomDocument &
amp;document, QObject *
parent)
:
QAbstractItemModel(parent),
domDocument(document),
rootItem(new
DomItem(domDocument, 0
))
{
}
A shallow copy of the document is stored for future reference, and a root item is created to provide a wrapper around the document. We assign the root item a row number of zero only to be consistent since the root item will have no siblings.
Since the model only contains information about the root item, the destructor only needs to delete this one item:
DomModel::
~
DomModel()
{
delete
rootItem;
}
All of the child items in the tree will be deleted by the DomItem destructor as their parent items are deleted.
Basic Properties of The Model▲
Some aspects of the model do not depend on the structure of the underlying document, and these are simple to implement.
The number of columns exposed by the model is returned by the columnCount() function:
int
DomModel::
columnCount(const
QModelIndex &
amp;parent) const
{
Q_UNUSED(parent);
return
3
;
}
This value is fixed, and does not depend on the location or type of the underlying node in the document. We will use these three columns to display different kinds of data from the underlying document.
Since we only implement a read-only model, the flags() function is straightforward to write:
Qt::
ItemFlags DomModel::
flags(const
QModelIndex &
amp;index) const
{
if
(!
index.isValid())
return
Qt::
NoItemFlags;
return
QAbstractItemModel::
flags(index);
}
Since the model is intended for use in a tree view, the headerData() function only provides a horizontal header:
QVariant DomModel::
headerData(int
section, Qt::
Orientation orientation,
int
role) const
{
if
(orientation ==
Qt::
Horizontal &
amp;&
amp; role ==
Qt::
DisplayRole) {
switch
(section) {
case
0
:
return
tr("Name"
);
case
1
:
return
tr("Attributes"
);
case
2
:
return
tr("Value"
);
default
:
break
;
}
}
return
QVariant();
}
The model presents the names of nodes in the first column, element attributes in the second, and any node values in the third.
Navigating The Document▲
QModelIndex DomModel::
index(int
row, int
column, const
QModelIndex &
amp;parent) const
{
if
(!
hasIndex(row, column, parent))
return
QModelIndex();
DomItem *
parentItem;
if
(!
parent.isValid())
parentItem =
rootItem;
else
parentItem =
static_cast
&
lt;DomItem*&
gt;(parent.internalPointer());
The function first has to relate the parent index to an item that contains a node from the underlying document. If the parent index is invalid, it refers to the root node in the document, so we retrieve the root item that wraps it; otherwise, we obtain a pointer to the relevant item using the QModelIndex::internalPointer() function. We are able to extract a pointer in this way because any valid model index will have been created by this function, and we store pointers to item objects in any new indexes that we create with QAbstra