Parsing Documents

Instantiate the Parser

To parse TOML documents, utilise the Parser class. This class is lightweight and designed to be instantiated as a local variable on the stack:

#include <erbsland/qt/toml/Parser.hpp>

using namespace elqt::toml;

void parseConfiguration() {
    Parser parser{};
    // ..
}

The parser’s constructor accepts an optional argument indicating the TOML Specification to be used. We advise sticking with the default constructor, as it automatically adopts the latest TOML version whenever a new release occurs.

If you need to restrict parsing to a specific TOML version or use an unreleased version, you can specify this using the optional argument:

#include <erbsland/qt/toml/Parser.hpp>

using namespace elqt::toml;

void parseConfiguration() {
    Parser parser{Specification::TOML_1_1}; // Use the TOML 1.1 specification
    // ..
}

File Parsing

The parseFileOrThrow() method is used to read and interpret TOML configuration files from disk. It requires one parameter: the absolute path to the file to be parsed.

#include <erbsland/qt/toml/Parser.hpp>

using namespace elqt::toml;

void parseConfiguration() {
    auto path = QStringLiteral("/path/to/config.toml");
    Parser parser{};
    try {
        auto toml = parser.parseFileOrThrow(path);
        // Process the TOML document here.
    } catch (const Error &error) {
        // Handle any parsing errors here.
    }
}

The parser will attempt to open, read, and parse the specified file. If any issues arise during this process, an exception is thrown.

The Error object of the exception provides valuable information about the problem that prevented successful parsing. For more details, see the Error Handling chapter.

Parsing Strings and Byte Data

If the TOML file has already been read by another component of your software, you can parse TOML data from a QString or QByteArray using the parseStringOrThrow() and parseDataOrThrow() methods, respectively.

#include <erbsland/qt/toml/Parser.hpp>

using namespace elqt::toml;

void parseConfiguration(const QString &tomlText) {
    Parser parser{};
    try {
        auto toml = parser.parseStringOrThrow(tomlText);
        // Process the TOML document here.
    } catch (const Error &error) {
        // Handle any parsing errors here.
    }
}

These methods work similarly to parseFileOrThrow(), with the key difference being that they won’t throw an Error::Type::IO error.

Note

Although parseFileOrThrow() and parseDataOrThrow() utilise the same strict built-in UTF-8 decoder, parseStringOrThrow() initially relies on the UTF-16 encoding inherent to the string. Any encoding errors will be detected when these values are read from the string.

Therefore, even with a QString, you may encounter Error::Type::Encoding errors. However, these errors would be due to issues with UTF-16 encoding, not UTF-8.

Thread Safety

Every function within the Erbsland Qt TOML parser is reentrant, meaning it can be safely executed concurrently from multiple threads. However, it’s essential that each thread operates on its own Parser instance.

To put it in simpler terms, a reentrant function is one that can be paused partway through its execution, called again (reentered), and then safely resumed from where it left off, without causing any conflicts or inconsistencies. This characteristic makes reentrant functions particularly useful in multi-threaded environments.

With Erbsland Qt TOML parser, you can have multiple threads parsing different TOML documents simultaneously, as long as each thread uses its own Parser instance.

Interacting with the Parsed Output

The parser generates the output as a nested hierarchy of Value objects. The returned object represents the root table of the document, and it always of the ValueType::Table type.

Each value in this structure can be of the following types Integer, Float, Boolean, String, Time, Date, DateTime, Table, and Array. For the latter two types, which are often checked, there are two dedicated functions: Value::isTable() and Value::isArray().

Instead of returning a value directly, the parser returns a std::shared_ptr<Value> pointing to the instance. This approach guarantees easy duplication and transfer of the parsed document’s structure to different parts of your application, eliminating the need for manual memory management. The library also defines ValuePtr you should use in declarations for this shared pointer.

Accessing Individual Values using Keys

We advise using the <type>Value(keypath) methods to access individual values, as demonstrated by the stringValue() function in the following example.

#include <erbsland/qt/toml/Value.hpp>

using namespace elqt::toml;

void useConfiguration(const ValuePtr &rootTable) {
    auto ipAddress = rootTable->stringValue(QStringLiteral("server.ip-address"), QStringLiteral("127.0.0.1"));
    // ...
}

These methods can process either a single key or a key path where each key is separated by a dot (.). If the key doesn’t exist or doesn’t match the expected type, the function will return the default value specified as the second argument.

Keys that contain a dot in the name are not supported by this convenience methods, if you have to work with unusual keys like this, there is the special method valueFromKey() for this case.

Array Iteration

To iterate over an array, you can use the begin() and end() methods, utilize one of the standard library’s algorithms, or employ the for loop syntax directly.

#include <erbsland/qt/toml/Value.hpp>

using namespace elqt::toml;

void useConfiguration(const ValuePtr &rootTable) {
    auto serverNames = rootTable->arrayValue(QStringLiteral("server_names"));
    // this directly accesses the vector within the value.
    for (const auto &entry : *serverNames) {
        // ...
    }
}

Alternatively, you can first convert the table value into a std::vector. In this scenario, as illustrated in the following example, the serverNames list becomes a copy of the vector contained in the value instance.

#include <erbsland/qt/toml/Value.hpp>

using namespace elqt::toml;

void useConfiguration(const ValuePtr &rootTable) {
    // this creates a copy of the vector stored in the value.
    auto serverNames = rootTable->arrayValue(QStringLiteral("server_names")).toArray();
    for (const auto &entry : serverNames) {
        // ...
    }
}

Retrieving All Keys and Values of a Table

There are two primary methods to access all keys and corresponding values in a table value. The first method involves using the tableKeys() function, followed by the valueForKey() function to fetch the value associated with a particular key.

#include <erbsland/qt/toml/Value.hpp>

using namespace elqt::toml;

void useConfiguration(const ValuePtr &rootTable) {
    auto ports = rootTable->arrayValue(QStringLiteral("ports"));
    for (const auto &key : ports->tableKeys()) {
        auto value = ports->valueForKey(key);
        // ...
    }
}

Alternatively, you can initially convert the value into a std::unordered_map, then iterate over the map’s entries as demonstrated in the following example:

#include <erbsland/qt/toml/Value.hpp>

using namespace elqt::toml;

void useConfiguration(const ValuePtr &rootTable) {
    auto ports = rootTable->arrayValue(QStringLiteral("ports"));
    for (const auto &[key, value] : ports->toTable()) {
        // ...
    }
}