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:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <name>Max Planck</name>
  <birth>1858</birth>
  <nobelprize>yes</nobelprize>
</root>

This is how you use QXmlGet to read its content:

QXmlGet xmlGet;
xmlGet.load("data.xml");
if (xmlGet.find("name"))
  QString name = xmlGet.getString(); // returns "Max Planck"
if (xmlGet.find("birth"))
  int birthYear = xmlGet.getInt(); // returns 1858
if (xmlGet.find("nobelprize"))
  bool nobelPrize = xmlGet.getBool(); // returns true

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).

Here’s how we can create such an XML file with QXmlPut:

QXmlPut xmlPut("root");
xmlPut.putString("name", "Max Planck");
xmlPut.putInt("birth", 1858);
xmlPut.putBool("nobelprize", true);
xmlPut.save("data.xml");

Now that was easy.

Nested tags

Currently, our XML file isn’t much better than a plain list of entries. XML’s speciality is organizing data in nested structures. So let’s improve our file structure (I’m skipping loading/saving code for brevity):

QXmlPut xmlPut("root");
xmlPut.descend("physicist");
xmlPut.putString("name", "Max Planck");
xmlPut.putInt("birth", 1858);
xmlPut.putBool("nobelprize", true);
xmlPut.rise();
xmlPut.descend("physicist");
xmlPut.putString("name", "Erwin Schrödinger");
xmlPut.putInt("birth", 1887);
xmlPut.putBool("nobelprize", true);
xmlPut.rise();

Which will produce:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <physicist>
    <name>Max Planck</name>
    <birth>1858</birth>
    <nobelprize>yes</nobelprize>
  </physicist>
  <physicist>
    <name>Erwin Schrödinger</name>
    <birth>1887</birth>
    <nobelprize>yes</nobelprize>
  </physicist>
</root>

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:

while (xmlGet.findNext("physicist"))
{
  xmlGet.descend();
  if (xmlGet.find("name"))
    QString name = xmlGet.getString(); // "Max Planck" in first, "Erwin Schrödinger" in second iteration
  (...)
  xmlGet.rise();
}

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:

<birth location="Kiel">1858</birth>
<nobelprize title="discovery of energy quanta" year="1919">yes</nobelprize>

Here we have a total of three attributes: location for the birth tag as well as title and year for the tag nobelprize.
Here’s how you can read such attributes:

if (xmlGet.find("birth"))
{
  QString birthPlace = xmlGet.getAttributeString("location", "unknown");
  int birthYear = xmlGet.getInt();
}
if (xmlGet.find("nobelprize"))
{
  QString nobelTitle = xmlGet.getAttributeString("title", "unknown");
  int nobelYear = xmlGet.getAttributeInt("year");
  bool nobelPrize = xmlGet.getBool();
}

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).

The writing part is straight forward, too:

xmlPut.putInt("birth", 1858);
xmlPut.setAttributeString("location", "Kiel");
xmlPut.putBool("nobelprize", true);
xmlPut.setAttributeString("title", "discovery of energy quanta");
xmlPut.setAttributeInt("year", 1919);

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.

Let’s look at the XML file first:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE SMData>
<storemanager>
  <inventory>
    <item category="3" name="Milk">
      <quantity>40</quantity>
      <price>1.85</price>
    </item>
    <item category="7" name="Orange">
      <quantity>53</quantity>
      <price>0.49</price>
    </item>
    <item category="7" name="Banana">
      <quantity>27</quantity>
      <price>1.29</price>
    </item>
  </inventory>
  <settings>
    <geometry width="943" left="137" height="543" top="422"/>
    <soldoutcolor>#ff1414</soldoutcolor>
  </settings>
</storemanager>

Each item is represented by a struct InventoryItem like this:

struct InventoryItem
{
  int category, quantity;
  QString name;
  double price;
};

And our data store is just a QList<InventoryItem> inventory;

Here’s how the XML file is generated (assuming the inventory list is filled with the three items Milk, Orange and Banana as shown above):

QXmlPut xmlPut("storemanager", "1.0", "UTF-8", true, "SMData");
writeXmlInventory(xmlPut.descended("inventory"));
writeXmlSettings(xmlPut.descended("settings"));
xmlPut.save("storemanager.xml");

// Using the functions:

void MainWindow::writeXmlSettings(QXmlPut xmlPut)
{
  xmlPut.putRect("geometry", geometry());
  xmlPut.putColor("soldoutcolor", QColor(255, 20, 20));
}

void MainWindow::writeXmlInventory(QXmlPut xmlPut)
{
  for (int i=0; i<inventory.size(); ++i)
    writeXmlInventoryItem(xmlPut.descended("item"), inventory.at(i));
}

void MainWindow::writeXmlInventoryItem(QXmlPut xmlPut, const InventoryItem &item)
{
  xmlPut.setAttributeString("name", item.name);
  xmlPut.setAttributeInt("category", item.category);
  xmlPut.putInt("quantity", item.quantity);
  xmlPut.putDouble("price", item.price);
}

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.

So here’s how to read that xml file back in:

QXmlGet xmlGet;
xmlGet.load("storemanager.xml");
if (xmlGet.docType() == "SMData")
{
  if (xmlGet.find("inventory"))
    readXmlInventory(xmlGet.descended());
  if (xmlGet.find("settings"))
    readXmlSettings(xmlGet.descended());
} else
  QMessageBox::critical(this, "StoreManager", "Wrong doctype in storemanager.xml!");

// Using the functions:

void MainWindow::readXmlSettings(QXmlGet xmlGet)
{
  if (xmlGet.find("geometry"))
    setGeometry(xmlGet.getRect(geometry())); // the fail-safe default geometry is the current geometry()
  if (xmlGet.find("soldoutcolor"))
    QColor soldOutColor = xmlGet.getColor(Qt::red); // fail-safe default color is red
}

void MainWindow::readXmlInventory(QXmlGet xmlGet)
{
  while (xmlGet.findNext("item"))
  {
    InventoryItem newItem = readXmlInventoryItem(xmlGet.descended());
    if (!newItem.name.isEmpty()) // don't add potentially corrupt data
      inventory.append(newItem);
  }
}

InventoryItem MainWindow::readXmlInventoryItem(QXmlGet xmlGet)
{
  InventoryItem item;
  item.name = xmlGet.getAttributeString("name"); // returns an empty string if "name" attribute not found
  item.category = xmlGet.getAttributeInt("category");
  if (xmlGet.find("quantity"))
    item.quantity = xmlGet.getInt();
  if (xmlGet.find("price"))
    item.price = xmlGet.getDouble();
  return item;
}

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:

QXmlGet xmlGet;
xmlGet.load("file.xml");
xmlGet.findAndDescend("someTag"); // combines find and descend in one call, see documentation
if (xmlGet.getAttributeBool("modifyme"))
{
  QXmlPut xmlPut(xmlGet); // here's QXmlPut's special constructor taking QXmlGet
  xmlPut.putString("childTag", "hello");
  xmlPut.save("file.xml");
}

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.

Download

QXmlPutGet source files: QXmlPutGet.tar.gz
QXmlPutGet documentation: QXmlPutGet-doc.tar.gz
Release date: 05.04.12

11 comments on “XML classes for Qt

  1. Wow! Awesome. I’ve been searching for a decent way to manipulate XML with QT. Your code really helped me out. Thank you very much!

  2. Hey there.
    Thanks for this great resource, just one question that I can’t figure out: How would I create a tag (I’m using QXmlPut to create html dynamically) blah, i.e. add an attribute and a content literal (not sub-element!) to the same element?

    • damn, the forum destroyed the tags…
      What I was getting at was a td-Element in an html table with the align=”center” attribute set and some content literal

  3. I’d recommend adding to the qt setup section for the people still kinda new, to add the cpp and h file to the .pro file, It took me about an hr to figure that out because the qt classes we don’t need to do that with.

  4. Nice work but unfortunately using QtGui. We have a very simple project that only depends on QtCore. XML core reading and writing shouldn’t need more than QtCore. Is it possible to make a version that relies only on QtCore depending on preprocessor flags?

    • If you think about it, quite the opposite of communism, right? I’m not allowing anyone to just take my code and use it under his flag without any restrictions. If you want non-GPL, I explicitly state that you may contact me via mail and we’ll find an agreement.
      GPL is the default license, not the only one.