Some aspects of the model do not depend on the structure of the underlying document, and these are simple to implement.
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.
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
The index() function creates a model index for the item with the given row, column, and parent in the model:
QModelIndex DomModel::index(int row, int column, const QModelIndex &parent)
const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
DomItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<DomItem*>(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 QAbstractItemModel::createIndex():
DomItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
A child item for the given row is provided by the parent item's child() function. If a suitable child item was found then we call createIndex() to produce a model index for the requested row and column, passing a pointer to the child item for it to store internally. If no suitable child item is found, an invalid model index is returned.
Note that the items themselves maintain ownership of their child items. This means that the model does not need to keep track of the child items that have been created, and can let the items themselves tidy up when they are deleted.
The number of rows beneath a given item in the model is returned by the rowCount() function, and is the number of child nodes contained by the node that corresponds to the specified model index:
int DomModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
DomItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<DomItem*>(parent.internalPointer());
return parentItem->node().childNodes().count();
}
To obtain the relevant node in the underlying document, we access the item via the internal pointer stored in the model index. If an invalid index is supplied, the root item is used instead. We use the item's node() function to access the node itself, and simply count the number of child nodes it contains.
Since the model is used to represent a hierarchical data structure, it needs to provide an implementation for the parent() function. This returns a model index that corresponds to the parent of a child model index supplied as its argument:
QModelIndex DomModel::parent(const QModelIndex &child) const
{
if (!child.isValid())
return QModelIndex();
DomItem *childItem = static_cast<DomItem*>(child.internalPointer());
DomItem *parentItem = childItem->parent();
if (!parentItem || parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
For valid indexes other than the index corresponding to the root item, we obtain a pointer to the relevant item using the method described in the index() function, and use the item's parent() function to obtain a pointer to the parent item.
If no valid parent item exists, or if the parent item is the root item, we can simply follow convention and return an invalid model index. For all other parent items, we create a model index containing the appropriate row and column numbers, and a pointer to the parent item we just obtained.
Data is provided by the data() function. For simplicity, we only provide data for the display role, returning an invalid variant for all other requests:
QVariant DomModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
DomItem *item = static_cast<DomItem*>(index.internalPointer());
QDomNode node = item->node();
As before, we obtain an item pointer for the index supplied, and use it to obtain the underlying document node. Depending on the column specified, the data we return is obtained in different ways:
QStringList attributes;
QDomNamedNodeMap attributeMap = node.attributes();
switch (index.column()) {
case 0:
return node.nodeName();
case 1:
for (int i = 0; i < attributeMap.count(); ++i) {
QDomNode attribute = attributeMap.item(i);
attributes << attribute.nodeName() + "=\""
+attribute.nodeValue() + "\"";
}
return attributes.join(" ");
case 2:
return node.nodeValue().split("\n").join(" ");
default:
return QVariant();
}
}
For the first column, we return the node's name. For the second column, we read any attributes that the node may have, and return a string that contains a space-separated list of attribute-value assignments. For the third column, we return any value that the node may have; this allows the contents of text nodes to be displayed in a view.
If data from any other column is requested, an invalid variant is returned.
Implementation Notes
Ideally, we would rely on the structure provided by QDomDocument to help us write the parent() and index() functions that are required when subclassing QAbstractItemModel. However, since Qt's DOM classes use their own system for dynamically allocating memory for DOM nodes, we cannot guarantee that the QDomNode objects returned for a given piece of information will be the same for subsequent accesses to the document.
We use item wrappers for each QDomNode to provide consistent pointers that the model can use to navigate the document structure.