Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Streaming XML in Qt with QXmlStreamReader and QXmlStreamWriter

Tech 1

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

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.