Qtの基礎 - CSVファイル

提供: MochiuWiki : SUSE, EC, PCB
📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

概要

CSV (Comma-Separated Values) ファイルは、テキストベースのデータ形式である。
各行がデータレコードを表しており、フィールドはカンマで区切られている。

Qtでは、CSVファイルの読み書きを簡単に行うことができる。

主にQFileクラス、QTextStreamクラス、QStringListを使用してCSVファイルを操作する。
QFileクラスはファイルのオープン・クローズを行い、QTextStreamクラスはテキストの読み書きを行う。
QStringListクラスは、CSVの各行を分割して扱う場合に便利である。

ファイルの読み込み時は、QFileオブジェクトでファイルを開き、QTextStreamオブジェクトを使用して1行ずつ読み込む。
各レコードは、QString::splitメソッドでフィールドに分割することができる。

書き込み時も同様、QFileオブジェクトとQTextStreamオブジェクトを使用する。
データをカンマで連結して、QTextStreamクラスの<<演算子を使用して書き込む。

また、ファイルがオープンできない、書き込むことができない等の状況に対応するため、適切な例外処理やエラー処理を実装することが推奨される。

なお、CSVファイルの形式が統一されていない場合 (例: フィールド内にカンマが含まれる)、パースが複雑になることがある。
このような場合は、正規表現やより高度なパーシング技術が必要になることもある。


CSVファイル

以下の例では、CSVファイルを作成している。

処理の流れを以下に示す。

  1. 数値データを生成する。
    ここでは、sin曲線である を生成する。
  2. 数値データをテキストデータに変換する。
  3. ファイル保存ダイアログを開いて、保存するファイル名を入力する。
  4. 全テキストデータを書き込む。


 #include <QFileDialog>
 #include <QMessageBox>
 #include <QTextStream>
 #include <QFile>
 #include <vector>
 #include <math.h>
 #include <sstream>
 #include "mainwindow.h"
 #include "ui_mainwindow.h"
 
 using namespace std;
 
 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
 {
    ui->setupUi(this);
 }
 
 MainWindow::~MainWindow()
 {
    delete ui;
 }
 
 void MainWindow::OnButtonClicked()
 {
    // Input File Name
    QString FileName = QFileDialog::getSaveFileName(this, tr("Save CSV File"), "", tr("CSV (*.csv);;All Files (*)"));
 
    if(FileName.isEmpty())
    {  // Cancel
       return;
    }
 
    // Generate CSV Data
    int iSize = 100;
    vector<double> lx(iSize, 0.0f),
                   ly(iSize, 0.0f);
 
    double dx = 2 * 3.15159 / iSize;
    for(int i = 0; i < iSize; i++)
    {
       lx[i] = i * dx;
       ly[i] = sin(vx);
    }
 
    // Convert from Double to String
    QString strFileData = "";
    //stringstream ss;
    for(int i = 0; i < iSize; i++)
    {
        strFileData += lx[i] + ", " + ly[i] + "\n";
        //ss << lx[i] << "," << ly[i] << endl;
    }
    
    // Write to File
    QFile File(FileName);

    if(!File.open(QIODevice::WriteOnly))
    { 
        QMessageBox::information(this, tr("Unable to open file"), File.errorString());
 
        return;
    }
 
    QTextStream OutStream(&File);
    // 浮動小数点をe形式にする
    //OutStream.setRealNumberNotation(QTextStream::ScientificNotation);
 
    // 浮動小数点の桁数を固定
    //OutStream.setRealNumberNotation(QTextStream::FixedNotation);
 
    // 浮動小数点の桁数を設定
    OutStream.setRealNumberPrecision(3);
 
    OutStream << strFileData;
    //OutStream << ss.str().c_str();
 
    File.close();
 }



CSVファイル (RFC4180準拠) の読み込み

RFC4180は、CSVファイルの標準的な形式を定義しており、これに従うことで互換性の高いCSV処理が可能になる。

以下の例では、RFC4180の規則に従ってCSVファイルを読み込んでいる。

  • ダブルクォートで囲まれたフィールド内のカンマと改行を正しく処理する。
  • フィールド内の二重引用符 ("") を単一の引用符 () として解釈する。
  • 各行の末尾の空白を除去する。


 #include <QFile>
 #include <QTextStream>
 #include <QStringList>
 #include <QDebug>
 
 QList<QStringList> readCSV(const QString &filename)
 {
    QList<QStringList> result;
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
       qDebug() << "ファイルを開けません:" << filename;
       return result;
    }
 
    QTextStream in(&file);
    in.setCodec("UTF-8");
 
    QString     line;
    QStringList fields;
    bool inQuotes = false;
    QString field;
 
    while (!in.atEnd()) {
       QChar ch;
       in >> ch;
 
       if (inQuotes) {
          if (ch == '"') {
             if (in.peek() == '"') {
                field += ch;
                in >> ch;  // 次の文字(二重引用符)を読み飛ばす
             }
             else {
                inQuotes = false;
             }
          }
          else {
             field += ch;
          }
       }
       else {
          if (ch == '"') {
             inQuotes = true;
          }
          else if (ch == ',' || ch == '\n' || in.atEnd()) {
             fields.append(field.trimmed());
             field.clear();
             if (ch == '\n' || in.atEnd()) {
                result.append(fields);
                fields.clear();
             }
          }
          else {
             field += ch;
          }
       }
    }
 
    file.close();
 
    return result;
 }



CSVファイル (RFC4180準拠) の書き込み

RFC4180は、CSVファイルの標準的な形式を定義しており、これに従うことで互換性の高いCSV処理が可能になる。

以下の例では、RFC4180の規則に従ってCSVファイルを書き込んでいる。

  • カンマ、引用符、改行を含むフィールドを適切にダブルクォートで囲む。
  • フィールド内の引用符を二重引用符 ("") にエスケープする。
  • 各行の終わりに改行を挿入する。


 #include <QFile>
 #include <QTextStream>
 #include <QStringList>
 #include <QDebug>
 
 bool writeCSV(const QString &filename, const QList<QStringList> &data)
 {
    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
       qDebug() << "ファイルを開けません:" << filename;
       return false;
    }
 
    QTextStream out(&file);
    out.setCodec("UTF-8");
 
    for (const QStringList &row : data) {
       for (int i = 0; i < row.size(); ++i) {
          QString field = row[i];
          bool needQuotes = field.contains(QRegExp("[,\"\n\r]"));
 
          if (needQuotes) {
             out << '"' << field.replace("\"", "\"\"") << '"';
          }
          else {
             out << field;
          }
 
          if (i < row.size() - 1) {
             out << ",";
          }
       }
       out << "\n";
    }
 
    file.close();
 
    return true;
 }



ストリーミング処理を使用したCSVファイルの読み込み

  • ストリーミング処理
    ファイル全体を1度にメモリに読み込むのではなく、1レコードずつ処理する。
    これにより、大規模なファイルでもメモリ使用量を抑えることができる。
  • コールバック関数
    各レコードの処理をコールバック関数として渡すことにより、柔軟な処理が可能になる。


 #include <QFile>
 #include <QTextStream>
 #include <QStringList>
 #include <functional>
 #include <QDebug>
 
 enum class CSVError {
    NoError,
    FileOpenError,
    ReadError,
    ParseError
 };
 
 class CSVReader
 {
 private:
    QString m_filename;
 
 public:
    CSVReader(const QString &filename) : m_filename(filename) {}
 
    CSVError readCSV(std::function<void(const QStringList&)> rowCallback)
    {
       QFile file(m_filename);
       if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
          qDebug() << "ファイルのオープンに失敗: " << m_filename;
          return CSVError::FileOpenError;
       }
 
       QTextStream in(&file);
       in.setCodec("UTF-8");
 
       QString line;
       QStringList fields;
       bool inQuotes = false;
       QString field;
       qint64 lineNumber = 0;
 
       while (!in.atEnd()) {
          QChar ch;
          in >> ch;
 
          if (in.status() != QTextStream::Ok) {
             qDebug() << "読み込みエラー:" << m_filename << "行:" << lineNumber;
             return CSVError::ReadError;
          }
 
          if (inQuotes) {
             if (ch == '"') {
                if (in.peek() == '"') {
                   field += ch;
                   in >> ch;  // 次の文字(二重引用符)を読み飛ばす
                }
                else {
                   inQuotes = false;
                }
             }
             else {
                field += ch;
             }
          }
          else {
             if (ch == '"') {
                inQuotes = true;
             }
             else if (ch == ',' || ch == '\n' || in.atEnd()) {
                fields.append(field.trimmed());
                field.clear();
                if (ch == '\n' || in.atEnd()) {
                   rowCallback(fields);
                   fields.clear();
                   lineNumber++;
                }
             }
             else {
                field += ch;
             }
          }
       }
 
       if (inQuotes) {
          qDebug() << "パースエラー: 閉じていない引用符があります。行:" << lineNumber;
          return CSVError::ParseError;
       }
 
       file.close();
 
       return CSVError::NoError;
    }
 };


 int main()
 {
    CSVReader reader("example.csv");
    CSVError error = reader.readCSV([](const QStringList& row) {
       // 各レコードの処理
       qDebug() << row;
    });
 
    switch (error) {
       case CSVError::NoError:
          qDebug() << "CSVファイルの読み込みに成功";
          break;
       case CSVError::FileOpenError:
          qDebug() << "ファイルのオープンに失敗";
          break;
       case CSVError::ReadError:
          qDebug() << "ファイルの読み込み中にエラーが発生";
          break;
       case CSVError::ParseError:
          qDebug() << "CSVのパース中にエラーが発生";
          break;
    }
 
    return 0;
 }