XML classes for Qt

QXmlPutGet is a pair of classes that allows handling of XML documents very conveniently. They are specifically made for writing application data to XML linearly (e.g. application configuration, session files, etc.) and reading them back in. Of course, they can also be used to juggle third party XML documents.

Qt currently offers three environments for working with XML: QXmlStreamReader/writer, DOM and SAX. To me, none of them feel as if they were making it as easy and intuitive as possible, to work with XML in a Qt application. Thus QXmlPutGet was created, which conceptually can be placed between the QXmlStreamReader/Writer and the DOM approach (internally, it uses DOM).

Setup

Get the latest version of QXmlPutGet from the download section at the bottom of this page.

Use the qxmlputget.h and .cpp file like any other ordinary class file

Documentation

The complete API documentation is available either online, or as a package in the download section. The package contains the documentation as a HTML hierarchy (the same you access online) and as a qch-help file for QtCreator/Assistant integration. If you use QtCreator or Assistant, you should definetly consider using the qch-help file, it’s great!
The integration of the qch file is pretty straight forward: Copy the qxmlputget.qch file to a place where it should be stored (e.g. the local QtCreator config directory). In QtCreator, go to the program settings and find the help section. In the tab Documentation or similar, you see a list of loaded documentation modules and some buttons to add/remove modules. Click the add button and select the qxmlputget.qch file. That’s it!
Now, when you place the cursor on any QXmlPutGet related class or function, press F1 and you find help just like you know it from Qt components.

Basic usage

The QXmlPutGet library consists of two classes: QXmlPut for writing and QXmlGet for reading XML.

First, Let’s see how we read simple XML files. Assume the following XML is inside the file data.xml:

the load function actually returns a boolean value we should have checked. It tells us whether the loading was successful. Further, it offers three output parameters (message, error line, error column) that carry information when the XML parsing failed.

As you can see, reading consists of two steps: navigating to the tag in the current hierarchy level with find and, if that returns true (i.e. a tag with the specified name was found), reading the content of the tag with the appropriate get(…) function. QXmlPutGet supports many Qt types for writing into tags, including basic types like QString, int, double and bool. But also more complex types like QSize, QRect, QPoint, QPen, QBrush, QFont, QColor, QDateTime, QStringList and even QByteArray and QImage can be written to/read from tags with such a simple interface (see the documentation for those functions).

So descend and rise are the magic functions that let us create nested tag hierarchies with QXmlPut. In QXmlGet, these functions exist, too. There, descend descends into the current tag (i.e. the one previously found by the find function) and rise leaves it again.

But what’s with the two tags, both called “physicist” in the top hierarchy level? The simple find function of QXmlGet will always just find Max Planck, i.e. the first occurence of the tag name. That’s where findNext comes into play:

findNext starts searching at the current element and returns true as long as it finds further entries with the specified tag name. If no more entries are found, it returns false once, and then resets the internal current element of the QXmlGet instance to the beginning of the hierarchy level (this means the current element is pointing to the parent of the hierarchy level). If, for any reason, you wish to reset the current element to the start manually, you can call findReset.

Tag attributes

Every XML tag can carry multiple attributes. Consider the following XML content:

We’ve changed the default return value to “unknown” for the birth location and nobelprize title. If the respective attribute can’t be found, this will be returned instead. All “get(…)” functions optionally accept such a default value. Before reading, we could have also checked, whether the attribute actually exists, with xmlGet.hasAttribute(QString name).

Nested tags with subroutines

When you work with sufficiently sophisticated XML structures, it makes sense to split the handling in separate, independent functions. QXmlPutGet was designed to allow this in an elegant manner. Both QXmlGet and QXmlPut are lightweight when copied, since nearly all their members use constant shared data, i.e. only references need to be copied. Of course, the underlying XML document isn’t copied either, since we want to access the same document with every copy of QXmlGet/Put.

Delegating XML work to another function works by passing a QXmlGet/Put instance (by value). The function can then use this copy, to do it’s work on the document, without disturbing the original instance (e.g. by forgetting to rise after a descend). Further, QXmlPut/Get offer two special functions, that return copies of themselves with useful properties in this situation:

The method restricted() returns an instance that isn’t allowed to rise beyond the current hierarchy. This way you can make sure certain functions don’t accidentally access portions of the XML file they shouldn’t have anything to do with. You will typically use this method when your main function needs to handle some XML inside the same hierarchy as the subfunction. When it’s done with its XML, it calls the subfunction with a restricted copy of the used QXmlPut/Get instance.

The method descended() is useful when the subfunction handles the hierarchy inside a tag completely on its own. It returns an instance of QXmlPut/Get that is descended in the current element (in QXmlPut it creates that element first, just like descend), and also restricted to it.

Enough theory, the following example makes use of this concept.

Slightly more realistic example

An application for managing a store has to save the inventory of the store. Further, we’ll want to save some user interface settings, too. We want three functions for this:, writeXmlInventory, writeXmlInventoryItem and writeXmlSettings. At the same time, In our XML file, the two shall each have their own hierarchy, in the tags <inventory> on the data side, and <settings> on the user interface side.

As you can see in the writeXmlInventoryItem function, the subfunction can access (here write) attributes to the parent tag. This is due to the fact, that the current element is the parent tag, when nothing else has been written to the hierarchy yet. It’s similar for QXmlGet, too: the current element is the parent tag when no navigation took place via find, findNext etc., or when findReset was just called.

Of course, the soldoutcolor is really just a dummy here, so the geometry tag doesn’t feel so alone in the settings hierarchy.

Reading from and writing to the same document

You may have noticed, that reading and writing was strictly separated. QXmlGet has no way of modifying the underlying XML document and QXmlPut has no way of reading from it. In many cases, as the store example above, this strict separation of responsibility is sensible and sufficient. However, if you need to work on XML content dynamically, e.g. perform modification- and reading operations interleaved, maybe even dependent on eachother, what we’ve learned so far wouldn’t help much.

QXmlGet and QXmlPut each have a special constructor to convert between the classes to solve this issue:

This code loads file.xml, descends into <someTag> and checks whether it has a boolean attribute called “modifyme” set to true, like this: <someTag modifyme="yes">. If that is so, it writes a <childTag>hello</childTag> entry inside the <someTag> hierarchy and saves the modified file.

Note that both xmlGet and xmlPut instances can safely be used alternatingly. So when xmlPut writes a new tag, xmlGet will be able to find and read it right away.

Currently, there’s no QXmlPutGet way of removing a tag from an existing XML document. If you need to do this, consider accessing the underlying QDomDocument via the document and element functions and doing it with the DOM API. Watch out that existing QXmlPut/Get instances aren’t on or inside the node about to be removed, or at least don’t use those specific instances after the remove operation, since they’ll be in an invalid state.