「Qtの基礎 - TOML」の版間の差分
|  (→同期処理) タグ: 差し戻し済み | タグ: 手動差し戻し | ||
| 219行目: | 219行目: | ||
|     ${TOMLPP_CFLAGS_OTHER} |     ${TOMLPP_CFLAGS_OTHER} | ||
|   ) |   ) | ||
|   </syntaxhighlight> |   </syntaxhighlight> | ||
| <br><br> | <br><br> | ||
2024年9月18日 (水) 20:07時点における版
概要
TOML (Tom's Obvious, Minimal Language) は、設定ファイルフォーマットとして設計された。
QtでTOMLを使用することは、アプリケーションの設定や構成を管理する上で有効である。
TOMLの特徴として、人間が読み書きしやすい形式であることが挙げられる。
キーと値のペアを基本構造とし、階層的なデータ構造も表現することができる。
日付や時刻、真偽値、数値等、様々なデータ型をサポートしている。
Qtには組み込みのTOMLパーサーが存在しないため、サードパーティ製ライブラリを使用する必要がある。
一般的に、toml11ライブラリやtoml++ライブラリ等のサードパーティ製ライブラリがよく使用されている。
これらのライブラリを使用することにより、TOMLファイルの読み込み、書き込み、データの解析が容易になる。
TOMLの構文は、基本的な形式は<キー> = <値>のペアで記述する。
セクションを使用してデータをグループ化することもでき、これはブラケット[]で囲むことで表現する。
TOMLを使用するメリットとして、設定の管理が容易になることが挙げられる。
例えば、アプリケーションの起動時にTOMLファイルから設定を読み込み、実行時に変更された設定をTOMLファイルに書き込むような使用方法が考えられる。
TOMLは他の設定ファイル形式 (JSONやYAML等) と比較して、より直感的で読みやすい構文を持っている。
特に複雑な階層構造を持つ設定データを扱う場合に、そのメリットが発揮される。
ただし、TOMLを使用する場合は、選択したライブラリの依存関係管理やパフォーマンスへの影響を考慮する必要がある。
TOMLの構文
TOMLファイルの主な構文要素を以下に示す。
- キーと値のペア
- 基本的な形式は、<キー名> = <値>である。
- 例: name = "TOML Example"
 
- 基本的な形式は、
- 文字列
- 基本文字列
- ダブルクォートで囲む
- "Hello"
 
- リテラル文字列
- シングルクォートで囲む
- 'C:\Users\username'
 
 
- 基本文字列
- 数値
- 整数
- 42
 
- 浮動小数点
- 3.14
 
 
- 整数
- ブーリアン
- true または false
 
- 日付と時刻
- ISO 8601形式を使用する。
- 例: date = 2023-03-27T15:32:00Z
 
- 配列
- 角括弧で囲む。
- 例: colors = [ "red", "yellow", "green" ]
 
- テーブル (セクション)
- 角括弧で囲んだ名前で定義する。
- 例:
- [database] server = "192.168.1.1" ports = [ 8001, 8001, 8002 ] 
 
- インラインテーブル
- 中括弧で囲む。
- 例: point = { x = 1, y = 2 }
 
- テーブルの配列
- 2重の角括弧で定義する。
- 例:
- [[fruits]] name = "apple" [[fruits]] name = "banana" 
 
- コメント
- シャープ記号 (#) を使用する。
 
- シャープ記号 (
toml++ライブラリ
toml++ライブラリとは
toml++ライブラリは、C++ 17以降で記述されたヘッダファイルのみのTOMパーサーおよびシリアライザーライブラリである。
このライブラリは、TOML v1.0.0仕様に完全準拠しており、使いやすい設計が特徴である。
主な特徴として、テンプレートベースの設計が挙げられる。
これにより、コンパイル時の型チェックが可能となり、実行時のパフォーマンスが向上する。
また、例外を使用しないエラーハンドリングをサポートしており、例外が禁止されている環境でも問題なく使用できる。
toml++は、UTF-8エンコーディングを完全にサポートしており、国際化されたアプリケーションでの使用が容易になる。
さらに、コメントの保持機能も備えており、TOMLファイルを読み込んで修正した後、元のコメントを保持したまま書き出すことができる。
toml++ライブラリは、単一ヘッダファイル版と複数ヘッダファイル版の両方が提供されており、プロジェクトの要件に応じて適切なものを選択することができる。
toml++ライブラリの使用例としては、設定ファイルの読み書き、データのシリアライズ / デシリアライズ、構造化されたデータの保存等が挙げられる。
C++の標準ライブラリと密接に統合されているため、std::stringクラスやその他のSTLコンテナとの相互運用性が高い。
パフォーマンス面では、toml++ライブラリは他の多くのTOMLパーサーよりも高速であることが報告されている。
大規模なTOMLファイルを扱う場合や、頻繁なパース / シリアライズ操作が必要なアプリケーションで特に威力を発揮する。
ドキュメンテーションも充実しており、GitHubリポジトリには詳細な使用方法や設定オプションが記載されている。
また、豊富な例やチュートリアルも提供されているため、初めて使用する開発者でも迅速に習得することができる。
toml++ライブラリのライセンス
toml++ライブラリのライセンスはMITライセンスに準拠しているため、商用プロジェクトを含む幅広い用途で自由に使用することができる。
toml++の主要なクラス
toml++ライブラリの主要クラスを以下に示す。
- toml::table
- TOMLのキー・値のペアを表現する中心的なクラスである。
- std::mapに似た動作をしており、TOMLドキュメント全体や入れ子になったテーブルの表現に使用する。
 
- toml::array
- TOMLの配列を表現するクラスであり、std::vectorに類似している。
- 異なる型の要素を含むことができる柔軟な配列である。
 
- toml::value
- TOMLの任意の値 (文字列、整数、浮動小数点数、ブーリアン、日付 / 時刻、配列、テーブル) を表現できる汎用的なクラスである。
- 型安全な方法で値を保持して、必要に応じて型変換を行う。
 
- toml::date
- toml::time
- toml::date_time
- これらのクラスは日付と時刻の処理に使用する。
- TOMLの日付・時刻形式を精密に表現して、操作するためのものである。
 
- toml::parse関数とtoml::formatterクラス
- パースとシリアライズの機能を提供する。
- toml::parseは、文字列やストリームからTOMLデータを読み込み、toml::formatterは、TOMLデータを文字列や出力ストリームに書き出す。
 
- toml::parse_result
- エラーハンドリングに使用されるクラスである。
- パース操作の結果を表し、成功時にはパースされたデータを、失敗時にはエラー情報を提供する。
 
- toml::node
- TOMLドキュメントの任意のノード (キー、値、テーブル、配列) を表現する抽象基底クラスである。
- 型に依存しない汎用的な操作が可能になる。
 
toml++ライブラリのインストール
パッケージ管理システムからインストール
# RHEL sudo dnf install tomlplusplus tomlplusplus-devel # SUSE -
ソースコードからインストール
toml++ライブラリのGithubにアクセスして、ソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。
tar xf tomlplusplus-<バージョン>.tar.gz cd tomlplusplus-<バージョン>
toml++ライブラリをビルドおよびインストールする。
mkdir build && cd build
# CMakeコマンドを使用する場合
cmake -DCMAKE_CXX_COMPILER=<G++ 8以降のG++> \
      -DCMAKE_BUILD_TYPE=Release           \
      -DCMAKE_INSTALL_PREFIX=<toml++ライブラリのインストールディレクトリ> \
      -DBUILD_EXAMPLES=ON                  \  # サンプルコードもインストールする場合
      ..
make -j $(nproc)
make install
# Meson & Ninjaを使用する場合
CXX=<G++ 8以降のG++> meson setup ./build                        \
                    -Dprefix=<toml++ライブラリのインストールディレクトリ> \
                    -Dbuild_examples=true  # サンプルコードもインストールする場合
ninja -C ./build -j $(nproc)
ninja -C ./build install
ライブラリの指定
Qtプロジェクトファイルを使用する場合
 # Qtプロジェクトファイル
 
 # pkg-configを使用してtoml++ライブラリを設定
 CONFIG += link_pkgconfig
 PKGCONFIG += tomlplusplus
 
 # pkg-configを使用しない場合
 LIBS += -ltomlplusplus
CMakeLists.txtファイルを使用する場合
 # CMakeLists.txtファイル
 
 # ...略
 
 find_package(PkgConfig REQUIRED)
 
 # toml+ライブラリの指定
 pkg_check_modules(TOMLPP REQUIRED tomlplusplus)
 
 # インクルードディレクトリの指定
 target_include_directories(${PROJECT_NAME} PRIVATE
   # ...略
   ${TOMLPP_INCLUDE_DIRS}
 )
 
 # ...略
 
 # ライブラリのリンク
 target_link_libraries(${PROJECT_NAME} PRIVATE
    # ...略
    ${TOMLPP_LIBRARIES}
 )
 
 # コンパイルオプションの設定
 target_compile_options(${PROJECT_NAME} PRIVATE
    # ...略
   ${TOMLPP_CFLAGS_OTHER}
 )
同期処理
TOMLの読み込み
以下の例では、toml++ライブラリを使用して、TOMLファイルを読み込んでいる。
toml++ライブラリを使用する場合は、プロジェクトにtoml++ライブラリをリンクする必要がある。
 #include <QFile>
 #include <QTextStream>
 #include <stdexcept>
 #include <toml++/toml.h>
 
 class TomlHandler
 {
 public:
    TomlHandler() = default;
 
    // TOMLファイルを読み込む
    toml::table readToml(const QString &filename)
    {
       try {
          return toml::parse_file(filename.toStdString());
       }
       catch (const toml::parse_error &err) {
          throw std::runtime_error(QString("TOMLファイルの解析エラー: %1").arg(err.description().c_str()).toStdString());
       }
    }
 
    // TOMLデータから特定の値を取得 (テンプレート関数)
    template<typename T>
    T getValue(const toml::table &data, const QString &key)
    {
       try {
          return data[key.toStdString()].value<T>();
       }
       catch (const toml::type_error &err) {
          throw std::runtime_error(QString("値の取得エラー: %1").arg(err.what()).toStdString());
       }
    }
 };
以下の例では、上記のクラスを使用してTOMLファイルを読み込んでいる。
 #include <QDebug>
 #include "TomlHandler.h"
 
 int main()
 {
    TomlHandler handler;
 
    try {
       // TOMLファイルを読み込む
       auto data = handler.readToml("config.toml");
 
       // 値を取得する
       QString name = handler.getValue<QString>(data, "name");
       int age = handler.getValue<int>(data, "age");
 
       qDebug() << "Name:" << name;
       qDebug() << "Age:" << age;
    }
    catch (const std::exception &e) {
       qCritical() << "エラー: " << e.what();
       return -1;
    }
 
    return 0;
 }
TOMLファイルの書き込み
以下の例では、toml++ライブラリを使用して、TOMLファイルを書き込んでいる。
toml++ライブラリを使用する場合は、プロジェクトにtoml++ライブラリをリンクする必要がある。
 #include <QFile>
 #include <QTextStream>
 #include <stdexcept>
 #include <toml++/toml.h>
 
 class TomlHandler
 {
 public:
    TomlHandler() = default;
 
    // TOMLファイルを読み込む
    toml::table readToml(const QString &filename)
    {
       try {
          return toml::parse_file(filename.toStdString());
       }
       catch (const toml::parse_error &err) {
          throw std::runtime_error(QString("TOMLファイルの解析エラー: %1").arg(err.description().c_str()).toStdString());
       }
    }
 
    // TOMLデータをファイルに書き込む
    void writeToml(const QString &filename, const toml::table &data)
    {
       QFile file(filename);
       if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
          throw std::runtime_error(QString("ファイルオープンエラー: %1").arg(filename).toStdString());
       }
 
       QTextStream out(&file);
       out << QString::fromStdString(toml::toml_formatter(data).format());
 
       file.close();
    }
 
    // TOMLデータから特定の値を取得 (テンプレート関数)
    template<typename T>
    T getValue(const toml::table &data, const QString &key)
    {
       try {
          return data[key.toStdString()].value<T>();
       }
       catch (const toml::type_error &err) {
          throw std::runtime_error(QString("値の取得エラー: %1").arg(err.what()).toStdString());
       }
    }
 
    // TOMLデータに値を設定する (テンプレート関数)
    template<typename T>
    void setValue(toml::table &data, const QString &key, const T &value)
    {
       data[key.toStdString()] = value;
    }
 };
以下の例では、上記のクラスを使用してTOMLファイルを書き込んでいる。
 #include <QDebug>
 #include "TomlHandler.h"
 
 int main()
 {
    TomlHandler handler;
 
    try {
       // TOMLファイルを読み込む
       auto data = handler.readToml("config.toml");
 
       // 値を取得
       QString name = handler.getValue<QString>(data, "name");
       int age = handler.getValue<int>(data, "age");
 
       qDebug() << "Name:" << name;
       qDebug() << "Age:" << age;
 
       // 値を設定
       handler.setValue(data, "location", "Tokyo");
 
       // 変更したデータを書き込む
       handler.writeToml("config_updated.toml", data);
    }
    catch (const std::exception &e) {
       qCritical() << "エラー: " << e.what();
       return -1;
    }
 
    return 0;
 }
非同期処理
TOMLの読み込み
以下の例では、toml++ライブラリを使用して、非同期でTOMLファイルを読み込んでいる。
toml++ライブラリを使用する場合は、プロジェクトにtoml++ライブラリをリンクする必要がある。
 // AsyncTomlHandler.hファイル
 
 #include <QObject>
 #include <QFile>
 #include <QTextStream>
 #include <QBuffer>
 #include <QFuture>
 #include <QtConcurrent>
 #include <stdexcept>
 #include <toml++/toml.h>
 
 class AsyncTomlHandler : public QObject
 {
    Q_OBJECT
 
 private:
    toml::table readTomlFromDevice(QIODevice* device)
    {
       std::unique_ptr<QIODevice> devicePtr(device); // デバイスの自動クリーンアップを保証
       return readTomlStream(device);
    }
  
 public:
    explicit AsyncTomlHandler(QObject *parent = nullptr) : QObject(parent) {}
 
    // TOMLファイルを非同期で読み込む
    QFuture<toml::table> readTomlAsync(const QString& filename)
    {
       return QtConcurrent::run([this, filename]() {
          return this->readTomlFromDevice(new QFile(filename));
       });
    }
 
    // TOMLデータをストリームから読み込む
    toml::table readTomlStream(QIODevice* device)
    {
       if (!device->open(QIODevice::ReadOnly | QIODevice::Text)) {
          throw std::runtime_error("デバイスを開けません");
       }
 
       QTextStream in(device);
       QString content = in.readAll();
       device->close();
 
       try {
          return toml::parse(content.toStdString());
       }
       catch (const toml::parse_error &err) {
          throw std::runtime_error(QString("TOMLデータの解析エラー: %1").arg(err.description().c_str()).toStdString());
       }
    }
 
    // メモリ上のTOMLデータを読み込む
    toml::table readTomlFromMemory(const QByteArray& data)
    {
       QBuffer buffer;
       buffer.setData(data);
       return readTomlStream(&buffer);
    }
 };
以下の例では、上記のクラスを使用してTOMLファイルを非同期で読み込んでいる。
 #include <QCoreApplication>
 #include <QFuture>
 #include <QFutureWatcher>
 #include <QDebug>
 #include "AsyncTomlHandler.h"
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    AsyncTomlHandler handler;
 
    // 非同期でTOMLファイルを読み込む
    QFuture<toml::table> readFuture = handler.readTomlAsync("config.toml");
    QFutureWatcher<toml::table> readWatcher;
    QObject::connect(&readWatcher, &QFutureWatcher<toml::table>::finished, [&]() {
       try {
          toml::table data = readWatcher.result();
          qDebug() << "Name:" << QString::fromStdString(data["name"].value_or(""));
          qDebug() << "Age:" << data["age"].value_or(0);
       }
       catch (const std::exception &e) {
          qCritical() << "エラー: " << e.what();
          QCoreApplication::quit();
       }
    });
 
    readWatcher.setFuture(readFuture);
 
    return a.exec();
 }
TOMLファイルの書き込み
以下の例では、toml++ライブラリを使用して、非同期でTOMLファイルを書き込んでいる。
toml++ライブラリを使用する場合は、プロジェクトにtoml++ライブラリをリンクする必要がある。
 // AsyncTomlHandler.hファイル
 
 #include <QObject>
 #include <QFile>
 #include <QTextStream>
 #include <QBuffer>
 #include <QFuture>
 #include <QtConcurrent>
 #include <stdexcept>
 #include <toml++/toml.h>
 
 class AsyncTomlHandler : public QObject
 {
    Q_OBJECT
 
 private:
    toml::table readTomlFromDevice(QIODevice *device)
    {
       std::unique_ptr<QIODevice> devicePtr(device); // デバイスの自動クリーンアップを保証
       return readTomlStream(device);
    }
 
    void writeTomlToDevice(QIODevice *device, const toml::table &data)
    {
       std::unique_ptr<QIODevice> devicePtr(device); // デバイスの自動クリーンアップを保証
       writeTomlStream(device, data);
    }
 
 public:
    explicit AsyncTomlHandler(QObject *parent = nullptr) : QObject(parent) {}
 
    // TOMLファイルを非同期で読み込む
    QFuture<toml::table> readTomlAsync(const QString &filename)
    {
       return QtConcurrent::run([this, filename]() {
          return this->readTomlFromDevice(new QFile(filename));
       });
    }
 
    // TOMLデータを非同期でファイルに書き込む
    QFuture<void> writeTomlAsync(const QString &filename, const toml::table &data)
    {
       return QtConcurrent::run([this, filename, data]() {
          this->writeTomlToDevice(new QFile(filename), data);
       });
    }
 
    // TOMLデータをストリームから読み込む
    toml::table readTomlStream(QIODevice *device)
    {
       if (!device->open(QIODevice::ReadOnly | QIODevice::Text)) {
          throw std::runtime_error("デバイスを開けません");
       }
 
       QTextStream in(device);
       QString content = in.readAll();
       device->close();
 
       try {
          return toml::parse(content.toStdString());
       }
       catch (const toml::parse_error &err) {
          throw std::runtime_error(QString("TOMLデータの解析エラー: %1").arg(err.description().c_str()).toStdString());
       }
    }
 
    // TOMLデータをストリームに書き込む
    void writeTomlStream(QIODevice *device, const toml::table &data)
    {
       if (!device->open(QIODevice::WriteOnly | QIODevice::Text)) {
          throw std::runtime_error("デバイスを開けません");
       }
 
       QTextStream out(device);
       out << QString::fromStdString(toml::toml_formatter(data).format());
 
       device->close();
    }
 
    // メモリ上のTOMLデータを読み込む
    toml::table readTomlFromMemory(const QByteArray &data)
    {
       QBuffer buffer;
       buffer.setData(data);
       return readTomlStream(&buffer);
    }
 
    // メモリ上にTOMLデータを書き込む
    QByteArray writeTomlToMemory(const toml::table &data)
    {
       QBuffer buffer;
       writeTomlStream(&buffer, data);
       return buffer.buffer();
    }
 };
以下の例では、上記のクラスを使用して、非同期でTOMLファイルを書き込んでいる。
 #include <QCoreApplication>
 #include <QFuture>
 #include <QFutureWatcher>
 #include <QDebug>
 #include "AsyncTomlHandler.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    AsyncTomlHandler handler;
 
    // 非同期でTOMLファイルを読み込む
    QFuture<toml::table> readFuture = handler.readTomlAsync("config.toml");
    QFutureWatcher<toml::table> readWatcher;
    QObject::connect(&readWatcher, &QFutureWatcher<toml::table>::finished, [&]() {
       try {
          toml::table data = readWatcher.result();
          qDebug() << "Name:" << QString::fromStdString(data["name"].value_or(""));
          qDebug() << "Age:" << data["age"].value_or(0);
 
          // データを変更
          data["location"] = "Tokyo";
 
          // 非同期で変更したデータを書き込む
          QFuture<void> writeFuture = handler.writeTomlAsync("config_updated.toml", data);
          QFutureWatcher<void> writeWatcher;
          QObject::connect(&writeWatcher, &QFutureWatcher<void>::finished, []() {
             qDebug() << "書き込み完了";
             QCoreApplication::quit();
          });
 
          writeWatcher.setFuture(writeFuture);
       }
       catch (const std::exception &e) {
          qCritical() << "エラー:" << e.what();
          QCoreApplication::quit();
       }
    });
 
    readWatcher.setFuture(readFuture);
 
    return a.exec();
 }
TOMLファイルの書き込み : メモリ操作 (ストリーミング処理)
以下の例では、toml++ライブラリを使用して、メモリ上のTOMLデータを書き込んでいる。
toml++ライブラリを使用する場合は、プロジェクトにtoml++ライブラリをリンクする必要がある。
- メモリ上のTOMLデータを読み込む。
- 読み込んだデータを表示して、一部のデータを変更する。
- 変更したデータをメモリに書き込み、結果を表示する。
 #include <QCoreApplication>
 #include <QDebug>
 #include "AsyncTomlHandler.h"
 
 int main(int argc, char *argv[])
 {
    QCoreApplication a(argc, argv);
 
    AsyncTomlHandler handler;
 
    // メモリ上のTOMLデータを定義
    QByteArray tomlData = R"(
       title = "TOMLサンプル"
       [owner]
       name = "山田太郎"
       age = 30
 
       [database]
       enabled = true
       ports = [ 8000, 8001, 8002 ]
       data = [ ["delta", "phi"], [3.14] ]
       temp_targets = { cpu = 79.5, case = 72.0 }
    )";
 
    try {
       // メモリからTOMLデータを読み込む
       toml::table data = handler.readTomlFromMemory(tomlData);
 
       // 読み込んだデータを表示
       qDebug() << "メモリから読み込んだTOMLデータ:";
       qDebug() << "タイトル:" << QString::fromStdString(data["title"].value_or(""));
       qDebug() << "オーナー名:" << QString::fromStdString(data["owner"]["name"].value_or(""));
       qDebug() << "オーナー年齢:" << data["owner"]["age"].value_or(0);
       qDebug() << "データベース有効:" << data["database"]["enabled"].value_or(false);
 
       // データを変更
       data["owner"]["location"] = "東京";
       data["database"]["version"] = "1.0.0";
 
       // 変更したデータをメモリに書き込む
       QByteArray updatedData = handler.writeTomlToMemory(data);
 
       // 更新されたデータを表示
       qDebug() << "更新されたTOMLデータ:";
       qDebug() << updatedData;
 
       // 更新されたデータを再度読み込んで確認
       toml::table updatedTable = handler.readTomlFromMemory(updatedData);
       qDebug() << "更新後のデータ確認:";
       qDebug() << "オーナー所在地:" << QString::fromStdString(updatedTable["owner"]["location"].value_or(""));
       qDebug() << "データベースバージョン:" << QString::fromStdString(updatedTable["database"]["version"].value_or(""));
    }
    catch (const std::exception &e) {
       qCritical() << "エラー: " << e.what();
    }
 
    QCoreApplication::quit();
 
    return a.exec();
 }