Streaming XML in Qt with QXmlStreamReader and QXmlStreamWriter
Streaming XML in Qt with QXmlStreamReader and QXmlStreamWriter aligns with SAX-style processing: data is consumed sequentially without building an in-memory tree. Compared to the legacy DOM and SAX APIs, the streaming classes are lighter, faster for large documents, and still easy to use for both reading and writing.
- QXmlStreamWriter: incremental, forward-only XML generation
- QXmlStreamReader: token-based, forward-only XML parsing
- Project note: add QT += xml to your .pro file (Qt 5)
Writing XML: serialize a directory listing
The snippet below walks the current directory, emits an XML document, and stores it to fileList.xml in the working directory.
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QFileInfoList>
#include <QXmlStreamWriter>
#include <QDebug>
static bool writeFileListXml()
{
const QString outFile = QStringLiteral("fileList.xml");
QFile f(outFile);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "Cannot open for write:" << outFile << f.errorString();
return false;
}
QXmlStreamWriter xml(&f);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
xml.setCodec("UTF-8");
#endif
xml.setAutoFormatting(true);
QDir dir(QDir::currentPath());
const QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
QDir::Name | QDir::DirsFirst);
xml.writeStartDocument();
xml.writeStartElement("files");
xml.writeStartElement("directory");
xml.writeAttribute("path", dir.absolutePath());
for (const QFileInfo &fi : entries) {
xml.writeTextElement("file", fi.fileName());
}
xml.writeEndElement(); // directory
xml.writeEndElement(); // files
xml.writeEndDocument();
f.close();
return !xml.hasError();
}
Example output
<?xml version="1.0" encoding="UTF-8"?>
<files>
<directory path="E:/work/sample">
<file>.qmake.stash</file>
<file>debug</file>
<file>fileList.xml</file>
<file>Makefile</file>
<file>Makefile.Debug</file>
<file>Makefile.Release</file>
<file>release</file>
<file>ui_widget.h</file>
</directory>
</files>
Reading XML: parse the directory listing
Using readNextStartElement() simplifies element-driven parsing and automatically skips whitespace.
#include <QXmlStreamReader>
static bool readFileListXml()
{
const QString inFile = QStringLiteral("fileList.xml");
QFile f(inFile);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Cannot open for read:" << inFile << f.errorString();
return false;
}
QXmlStreamReader xml(&f);
while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("files")) {
while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("directory")) {
const auto attrs = xml.attributes();
if (attrs.hasAttribute("path")) {
qDebug() << attrs.value("path").toString();
}
while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("file")) {
const QString fileName = xml.readElementText();
qDebug() << QStringLiteral("Filename: %1").arg(fileName);
} else {
xml.skipCurrentElement();
}
}
} else {
xml.skipCurrentElement();
}
}
} else {
xml.skipCurrentElement();
}
}
if (xml.hasError()) {
qWarning() << "XML error:" << xml.errorString();
return false;
}
return true;
}
Alternative token-driven read flow (explicit readNext + text())
static void scanTokens(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
const auto token = xml.readNext();
if (token == QXmlStreamReader::StartElement) {
const QString tag = xml.name().toString();
if (tag == QLatin1String("file")) {
xml.readNext(); // move to Characters
qDebug() << "Filename:" << xml.text().toString();
}
}
}
}
Empty elements vs text elements with QXmlStreamWriter
- writeEmptyElement("tag"): emits <tag ... /> and cannot contain child elements or text
- writeStartElement("tag") ... writeEndElement(): emits a normal start/end pair and may contain children or text
- writeTextElement("tag", "value"): emits value
The three variants below highlight structural differences when writing an annotation with an optional 3D position.
Variant A: position as an empty element with attributes
struct Annotation {
QString comments;
bool hasPosition = false;
double pos[3] = {0, 0, 0};
};
static bool writeAnnotationA(QXmlStreamWriter *w, const Annotation &a)
{
if (!w) return false;
QString s;
w->writeStartElement("annotation");
if (!a.comments.isEmpty())
w->writeAttribute("comments", a.comments);
if (a.hasPosition) {
w->writeEmptyElement("position");
w->writeAttribute("x", s.setNum(a.pos[0]));
w->writeAttribute("y", s.setNum(a.pos[1]));
w->writeAttribute("z", s.setNum(a.pos[2]));
w->writeEmptyElement("hint"); // another empty node
}
w->writeStartElement("section");
w->writeTextElement("name", "example");
w->writeEndElement(); // section
w->writeEndElement(); // annotation
return !w->hasError();
}
Variant B: position with an empty child element written as a pair
static bool writeAnnotationB(QXmlStreamWriter *w, const Annotation &a)
{
if (!w) return false;
QString s;
w->writeStartElement("annotation");
if (!a.comments.isEmpty())
w->writeAttribute("comments", a.comments);
if (a.hasPosition) {
w->writeStartElement("position");
w->writeAttribute("x", s.setNum(a.pos[0]));
w->writeAttribute("y", s.setNum(a.pos[1]));
w->writeAttribute("z", s.setNum(a.pos[2]));
w->writeStartElement("hint");
w->writeEndElement(); // hint (empty content, but written as start/end)
w->writeEndElement(); // position
}
w->writeStartElement("section");
w->writeTextElement("name", "example");
w->writeEndElement(); // section
w->writeEndElement(); // annotation
return !w->hasError();
}
Variant C: position with attributes only (no children)
static bool writeAnnotationC(QXmlStreamWriter *w, const Annotation &a)
{
if (!w) return false;
QString s;
w->writeStartElement("annotation");
if (!a.comments.isEmpty())
w->writeAttribute("comments", a.comments);
if (a.hasPosition) {
w->writeStartElement("position");
w->writeAttribute("x", s.setNum(a.pos[0]));
w->writeAttribute("y", s.setNum(a.pos[1]));
w->writeAttribute("z", s.setNum(a.pos[2]));
w->writeEndElement(); // position
}
w->writeStartElement("section");
w->writeTextElement("name", "example");
w->writeEndElement(); // section
w->writeEndElement(); // annotation
return !w->hasError();
}
Reading the annotation with QXmlStreamReader
Use readNextStartElement() to step into child elements and readElementText() for text nodes. Skipping unknown elements is important for forward copmatibility.
struct ParsedAnnotation {
QString comments;
bool hasPosition = false;
double x = 0, y = 0, z = 0;
};
static bool readAnnotation(QXmlStreamReader *r, ParsedAnnotation &out)
{
if (!r || r->name() != QLatin1String("annotation"))
return false;
auto attrs = r->attributes();
if (attrs.hasAttribute("comments"))
out.comments = attrs.value("comments").toString();
while (r->readNextStartElement()) {
const auto tag = r->name();
if (tag == QLatin1String("position")) {
attrs = r->attributes();
if (!attrs.hasAttribute("x") || !attrs.hasAttribute("y") || !attrs.hasAttribute("z")) {
qWarning() << "Position missing x, y, or z";
return false;
}
out.x = attrs.value("x").toDouble();
out.y = attrs.value("y").toDouble();
out.z = attrs.value("z").toDouble();
out.hasPosition = true;
r->skipCurrentElement(); // consume <position .../>, or its subtree if non-empty
} else if (tag == QLatin1String("section")) {
while (r->readNextStartElement()) {
if (r->name() == QLatin1String("name")) {
const QString name = r->readElementText();
Q_UNUSED(name);
} else {
r->skipCurrentElement();
}
}
} else {
r->skipCurrentElement();
}
}
if (r->hasError()) {
qWarning() << "Reader error" << r->errorString();
return false;
}
return true;
}
I/O targets: QFile vs QString
Writing to and reading from a QFile
{
QFile file("annotation.xml");
if (file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
QXmlStreamWriter w(&file);
w.writeStartDocument();
// write ...
w.writeEndDocument();
file.close();
}
}
{
QFile file("annotation.xml");
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QXmlStreamReader r(&file);
// read ...
file.close();
}
}
Writing too and reading from a QString buffer
QString xmlBuffer;
{
QXmlStreamWriter w(&xmlBuffer);
w.setAutoFormatting(true);
w.writeStartDocument();
// write ...
w.writeEndDocument();
}
{
QXmlStreamReader r(xmlBuffer);
// read ...
}