15.3.1.  DOM Tree Walking

[ fromfile: xml-dom.xml id: domtreewalking ]

Qt provides full read/write access to trees of XML data. Nodes can be navigated through an interface which is similar to, but slightly different from the QObject interface. Under the surface, SAX performs the parsing, and DOM defines a content handler that creates the tree of objects in memory. All client code needs to do is call setContent(), which causes the input to be parsed and the tree to be generated.

Example 15.8. src/xml/domwalker/main.cpp

[ . . . . ]
int main(int argc, char **argv) {
    QApplication app(argc, argv);
    QString filename;
    if (argc < 2) {
        cout << "Usage: " << argv[0] << " filename.xml" << endl;
        filename = "samplefile.xml";
    }
    else {
        filename = argv[1];
    }
    QFile f(filename);
    QString errorMsg;
    int errorLine, errorColumn;
    QDomDocument doc("SlackerDoc");
    bool result = doc.setContent(&f, &errorMsg, 
         &errorLine, &errorColumn);             1
    QDomNode before = doc.cloneNode(true);      2
    Slacker slack(doc);                         3
    QDomNode after = slack.transform();         4
    cout << QString("Before: ") << before << endl;
    cout << QString("After: ") << after << endl;
    QWidget * view = twinview(before, after);   5
    view->show();
    app.exec();
    delete view;
}
[ . . . . ]

1

Parse the file into a DOM tree, and store tree in empty doc.

2

Deep copy.

3

Send the tree to slack.

4

Start the visitation.

5

Create a pair of QTreeView objects separated by slider, using the QDomDocuments as models.


Example 15.8 transforms an XML document in-place. After the tree is manipulated, it is serialized to a QTextStream where it becomes savable and parsable again. Figure 15.4 shows the main classes used in this example.

Figure 15.4.  Domwalker and Slacker

Domwalker and Slacker

Slacker is derived from DomWalker, an application specialized for walking through DOM trees.

Example 15.9. src/xml/domwalker/domwalker.cpp

[ . . . . ]
QDomDocument DomWalker::transform() {
    walkTree(m_Doc);
    return m_Doc;
}

QDomNode DomWalker::walkTree(QDomNode current) {
    QDomNodeList dnl = current.childNodes();             1
    for (int i=dnl.count()-1; i >=0; --i)
        walkTree(dnl.item(i));
    if (current.nodeType() == QDomNode::ElementNode) {   2
       QDomElement ce = current.toElement();             3
       return visit(ce);
    }
    return current;
}
[ . . . . ]

1

First process children recursively.

2

Only process elements, leaving other nodes unchanged.

3

Instead of a typecast.


The walkTree() method, defined in Example 15.9, contains no pointers or typecasts. The QDom(Node|Element|Document|Attribute) types are smart-pointers. We "downcast" from QDomNode to QDomElement, or QDomXXX , using QDomNode::toElement() or toXXX() conversion functions.

[Tip] Tip

When traversing a tree, it is possible to use only the QDomNode interface, but "casting down" to QDomElement adds some convenient functions for dealing with the element as a whole, with its attributes (which are QDomNode child objects themselves).

Even though in this example QDomNode/QDomElement objects are being passed and returned by value, it is still possible to change the underlying DOM objects through the temporary copies. Through interface trickery, QDom objects look and feel like Java-style references, and hold pointers inside, rather than actual data.

Slacker defines how to transform documents from one XML format to another. It is an extension of DomWalker and overrides just one method, visit(). Defined in Example 15.10, this method has a special rule for each kind of element.

Example 15.10. src/xml/domwalker/slacker.cpp

[ . . . . ]
QDomElement Slacker::visit(QDomElement element) {
    QString name = element.tagName();
    
     
    QString cvalue = element.attribute("c", QString()) ;
    if (cvalue != QString()) {                  1
        element.setAttribute("condition", cvalue);
        element.setAttribute("c", QString());
    }
    [ . . . . ]
    if (name == "b") {
        element.setAttribute("role", "strong");
        element.setTagName("emphasis");
        return element;
    }
    if (name == "li") {                         2
        QDomNode parent = element.parentNode();
        QDomElement listitem = createElement("listitem");
        parent.replaceChild(listitem, element); 3
        element.setTagName("para");             4
        listitem.appendChild(element);
        return listitem;
    }
    [ . . . . ]

1

Modifying attributes - any c= becomes condition=

2

This transformation is more interesting because we replace <li> text </li> with <listitem><para> text </para></listitem>.

3

Remove the li tag, put a listitem in its place.

4

Modify tag name in-place.


When run, this example pops up a window like Figure 15.5, with two tree views shown side-by-side, letting us inspect the XML documents before and after the transformation.

Figure 15.5.  XML Tree Views

XML Tree Views