Qt 正規表現キャプチャ QRegExp::capturedTexts() の使い方と注意点

2025-05-27

QRegExp::capturedTexts() は、QRegExp オブジェクトを使って文字列に対して正規表現によるマッチングを行った後に、キャプチャされた部分文字列(グループ)をリスト形式で取得するための関数です。

もう少し詳しく説明します。

正規表現におけるキャプチャ(グループ化)とは?

正規表現のパターンの中で、() (丸括弧)で囲まれた部分は「キャプチャグループ」と呼ばれます。マッチングが行われた際、この括弧で囲まれた部分に対応する文字列が記憶されます。

例えば、正規表現 "(\\d+)-(\\w+)" を考えます。

  • (\\w+) は、1つ以上の単語構成文字(アルファベット、数字、アンダースコア)にマッチし、2番目のキャプチャグループとして記憶されます。
  • - は、ハイフンにマッチします。
  • (\\d+) は、1つ以上の数字にマッチし、最初のキャプチャグループとして記憶されます。

もし、この正規表現を文字列 "123-abc" に適用すると、

  • 2番目のキャプチャグループ (\\w+) にマッチした "abc" が記憶されます。
  • 最初のキャプチャグループ (\\d+) にマッチした "123" が記憶されます。
  • 全体がマッチします。

QRegExp::capturedTexts() の役割

QRegExp::capturedTexts() 関数は、QRegExp オブジェクトが文字列とのマッチングに成功した後で呼び出すことで、これらのキャプチャされた部分文字列を QStringList 型のリストとして返します

返されるリストの最初の要素(インデックス 0)は、マッチした文字列全体です。その後の要素(インデックス 1, 2, ...) には、正規表現パターン内で定義された各キャプチャグループにマッチした部分文字列が、定義された順に格納されます。

具体的な使用例

#include <QCoreApplication>
#include <QRegExp>
#include <QStringList>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString text = "Name: John Doe, Age: 30";
    QRegExp rx("Name: (\\w+) (\\w+), Age: (\\d+)");

    if (rx.indexIn(text) != -1) {
        QStringList capturedList = rx.capturedTexts();
        qDebug() << "マッチした全体:" << capturedList.at(0);   // "Name: John Doe, Age: 30"
        qDebug() << "名前 (姓):" << capturedList.at(1);      // "John"
        qDebug() << "名前 (名):" << capturedList.at(2);      // "Doe"
        qDebug() << "年齢:" << capturedList.at(3);         // "30"
    } else {
        qDebug() << "マッチしませんでした。";
    }

    return a.exec();
}

この例では、正規表現 "Name: (\\w+) (\\w+), Age: (\\d+)" を使って文字列 "Name: John Doe, Age: 30" を検索しています。

  • 最後の (\\d+) は年齢にマッチし、3番目のキャプチャグループとなります。
  • 次の (\\w+) は姓にマッチし、2番目のキャプチャグループとなります。
  • 最初の (\\w+) は名前にマッチし、最初のキャプチャグループとなります。

rx.indexIn(text)-1 でない場合(つまり、マッチに成功した場合)、rx.capturedTexts() を呼び出すことで、マッチした全体と各キャプチャグループにマッチした部分文字列を含む QStringList が返されます。



マッチング失敗後に capturedTexts() を呼び出す

エラー
QRegExp::indexIn()QRegExp::exactMatch() などのマッチング関数が false(または indexIn()-1)を返した場合、つまりマッチングが成功しなかった後に capturedTexts() を呼び出すと、未定義の動作を引き起こす可能性があります。通常は空の QStringList が返されますが、状況によっては不正な値やクラッシュにつながる可能性もあります。

トラブルシューティング

  • 必ずマッチングの成否を確認してから capturedTexts() を呼び出すようにしてください。

    QRegExp rx("(\\d+)");
    QString text = "abc";
    if (rx.indexIn(text) != -1) {
        QStringList capturedList = rx.capturedTexts();
        // ... キャプチャされたテキストを処理 ...
    } else {
        qDebug() << "マッチング失敗";
    }
    

キャプチャグループの数と期待するリストのサイズ

エラー
正規表現パターンに定義したキャプチャグループの数と、capturedTexts() が返すリストのサイズについての誤解。capturedTexts() は、マッチした全体の文字列を最初の要素(インデックス 0)として含み、その後にキャプチャグループにマッチした文字列が続きます。したがって、キャプチャグループが n 個ある場合、リストのサイズは n + 1 になります。

トラブルシューティング

  • 特定のキャプチャグループのテキストにアクセスする際は、インデックスが 1 から始まることに注意してください(インデックス 1 が最初のキャプチャグループ、インデックス 2 が2番目、...)。

    QRegExp rx("(\\w+)-(\\d+)"); // 2つのキャプチャグループ
    QString text = "apple-123";
    if (rx.indexIn(text) != -1) {
        QStringList capturedList = rx.capturedTexts();
        qDebug() << "リストのサイズ:" << capturedList.size(); // 3 (マッチ全体 + 2つのグループ)
        qDebug() << "マッチ全体:" << capturedList.at(0);   // "apple-123"
        qDebug() << "最初のグループ:" << capturedList.at(1);  // "apple"
        qDebug() << "2番目のグループ:" << capturedList.at(2);  // "123"
    }
    
  • 返されるリストのサイズを確認する際は、最初の要素がマッチ全体であることを考慮してください。

  • キャプチャグループの数を正確に把握してください。正規表現パターン内の () のペアの数がキャプチャグループの数です。

ネストされたキャプチャグループの扱い

考慮事項
正規表現パターン内でキャプチャグループがネストされている場合、capturedTexts()外側のグループから内側のグループの順にキャプチャされたテキストを返します。


正規表現 "((A)(B))" を文字列 "AB" に適用した場合、

  • 3番目のキャプチャグループ (B)"B" にマッチします。
  • 2番目のキャプチャグループ (A)"A" にマッチします。
  • 最初のキャプチャグループ ((A)(B))"AB" にマッチします。

capturedTexts(){"AB", "AB", "A", "B"} というリストを返します。

トラブルシューティング

  • 目的のキャプチャグループのインデックスを間違えないように注意してください。
  • ネストされたキャプチャグループがある場合は、返されるリストの順序を正確に理解しておく必要があります。

量指定子を含むキャプチャグループの繰り返し

考慮事項
量指定子 (*, +, {n,m} など) を含むキャプチャグループが繰り返しマッチした場合、capturedTexts()最後にマッチした部分文字列のみをそのグループのテキストとして保持します。


正規表現 "(\\d)+" を文字列 "12345" に適用した場合、(\\d)+"1", "12", "123", "1234", "12345" とマッチしますが、capturedTexts() の2番目の要素(インデックス 1)には最後にマッチした "5" のみが格納されます。

トラブルシューティング

  • QRegExp::pos()QRegExp::cap() などを組み合わせて、すべてのマッチ位置とテキストを取得する方法も検討できます。
  • 繰り返されるパターンのすべてのマッチをキャプチャしたい場合は、正規表現の設計を見直すか、マッチングを繰り返し行う必要があります。

グローバルマッチング (QRegExp::global) の影響

考慮事項
QRegExp オブジェクトがグローバルマッチング (rx.setMinimal(true)) モードになっている場合でも、capturedTexts()最後に成功したマッチの結果のみを返します。グローバルマッチングは、文字列内でパターンが複数回出現するかどうかを調べるために使用され、それぞれのマッチに対するキャプチャされたテキストを個別に取得するには、indexIn() をループ内で繰り返し呼び出す必要があります。

トラブルシューティング

  • 文字列内のすべてのマッチとそれに対応するキャプチャされたテキストを取得したい場合は、while (rx.indexIn(text, offset) != -1) のようなループ構造を使用し、各マッチの後に capturedTexts() を呼び出す必要があります。offset を適切に更新して、次の検索を開始位置を調整します。

正規表現パターンの誤り

エラー
最も一般的な問題は、正規表現パターン自体に誤りがあることです。これにより、意図した部分がキャプチャされなかったり、そもそもマッチングが失敗したりします。

トラブルシューティング

  • キャプチャしたい部分が () で囲まれているか確認してください。
  • 特殊文字のエスケープ (\\) が適切に行われているか確認してください。
  • 正規表現パターンを慎重に確認してください。オンラインの正規表現テスターなどを利用して、パターンが意図通りに動作するかどうかを確認することをお勧めします。

文字エンコーディングの問題

考慮事項
Qt は Unicode を基本としていますが、外部からのデータやファイル操作などで異なる文字エンコーディングが混在している場合、正規表現のマッチングが期待通りに行われないことがあります。

トラブルシューティング

  • 正規表現パターン内で特定の文字コードに依存するような記述は避けるようにしてください。
  • 処理する文字列のエンコーディングを確認し、必要に応じて QString::fromUtf8()QString::fromLocal8Bit() などの関数を使用して QString に変換してください。


例1: 単一のマッチとキャプチャ

この例では、簡単な正規表現を使って文字列から特定の情報を抽出し、capturedTexts() で取得します。

#include <QCoreApplication>
#include <QRegExp>
#include <QStringList>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString logEntry = "[INFO] 2023-10-27 10:30:00 - Application started.";
    QRegExp rx("\\[(\\w+)\\] (\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) - (.*)");

    if (rx.indexIn(logEntry) != -1) {
        QStringList capturedList = rx.capturedTexts();
        qDebug() << "ログエントリ全体:" << capturedList.at(0); // "[INFO] 2023-10-27 10:30:00 - Application started."
        qDebug() << "ログレベル:" << capturedList.at(1);     // "INFO"
        qDebug() << "タイムスタンプ:" << capturedList.at(2); // "2023-10-27 10:30:00"
        qDebug() << "メッセージ:" << capturedList.at(3);    // "Application started."
    } else {
        qDebug() << "ログエントリの形式が一致しませんでした。";
    }

    return a.exec();
}

このコードでは、ログエントリの形式を解析し、ログレベル、タイムスタンプ、メッセージをキャプチャしています。capturedTexts() は、マッチ全体、そして括弧で囲まれた各グループにマッチした文字列を順番に返します。

例2: 複数のマッチとキャプチャ (グローバルマッチ)

この例では、グローバルマッチングを使用して、文字列内に出現するすべてのパターンを検出し、それぞれのマッチでキャプチャされたテキストを取得します。

#include <QCoreApplication>
#include <QRegExp>
#include <QStringList>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString text = "apple:100, banana:200, cherry:150";
    QRegExp rx("(\\w+):(\\d+)");
    rx.setMinimal(true); // グローバルマッチを有効にする

    int pos = 0;
    while ((pos = rx.indexIn(text, pos)) != -1) {
        QStringList capturedList = rx.capturedTexts();
        qDebug() << "果物:" << capturedList.at(1) << ", 価格:" << capturedList.at(2);
        pos += rx.matchedLength(); // 次のマッチング開始位置を更新
    }

    return a.exec();
}

ここでは、setMinimal(true) を設定してグローバルマッチを有効にしています。indexIn() をループ内で繰り返し呼び出し、各マッチの開始位置を更新することで、文字列内のすべての "果物:価格" のペアを検出し、それぞれの果物名と価格を capturedTexts() で取得しています。

例3: 名前付きキャプチャグループ (Qt 5.15 以降)

Qt 5.15 以降では、名前付きキャプチャグループを使用できます。これにより、キャプチャされたテキストに名前でアクセスできるようになり、コードの可読性が向上します。

#include <QCoreApplication>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString text = "User: Alice (ID: 123)";
    QRegularExpression rx("User: (?<name>\\w+) \\(ID: (?<id>\\d+)\\)");
    QRegularExpressionMatch match = rx.match(text);

    if (match.hasMatch()) {
        qDebug() << "マッチ全体:" << match.captured(0);
        qDebug() << "名前:" << match.captured("name");
        qDebug() << "ID:" << match.captured("id");
    } else {
        qDebug() << "マッチしませんでした。";
    }

    return a.exec();
}

この例では、(?<name>\\w+)(?<id>\\d+) のように、キャプチャグループに "name" と "id" という名前を付けています。QRegularExpressionMatch オブジェクトの captured() 関数に名前を渡すことで、対応するキャプチャされたテキストを直接取得できます。注意: 名前付きキャプチャグループは QRegExp ではなく、QRegularExpression で利用可能です。

例4: 複数のキャプチャグループと条件分岐

この例では、複数のキャプチャグループを持つ正規表現を使用し、キャプチャされた内容に応じて処理を分岐させます。

#include <QCoreApplication>
#include <QRegExp>
#include <QStringList>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString data1 = "SET value=10";
    QString data2 = "GET value";
    QRegExp rxSet("^SET (\\w+)=(\\d+)$");
    QRegExp rxGet("^GET (\\w+)$");

    if (rxSet.exactMatch(data1)) {
        QStringList capturedList = rxSet.capturedTexts();
        QString key = capturedList.at(1);
        int value = capturedList.at(2).toInt();
        qDebug() << "SET コマンド - キー:" << key << ", 値:" << value;
    } else if (rxGet.exactMatch(data2)) {
        QStringList capturedList = rxGet.capturedTexts();
        QString key = capturedList.at(1);
        qDebug() << "GET コマンド - キー:" << key;
    } else {
        qDebug() << "不明なコマンド形式です。";
    }

    return a.exec();
}


QString の文字列操作関数

簡単なパターンや固定された文字列の検索、抽出であれば、正規表現を使わずに QString クラスが提供するメンバ関数で十分な場合があります。

  • QString::split()
    特定の区切り文字で文字列を分割し、QStringList を生成します。
  • QString::mid() / QString::left() / QString::right() / QString::section()
    文字列の一部を抽出します。
  • QString::contains()
    文字列が特定の文字列を含んでいるか確認します。
  • QString::startsWith() / QString::endsWith()
    文字列が特定のプレフィックスやサフィックスで始まる/終わるか確認します。
  • QString::indexOf() / QString::lastIndexOf()
    特定の文字列や文字の位置を検索します。


カンマ区切りの文字列から要素を抽出する場合

QString data = "apple,banana,cherry";
QStringList items = data.split(',');
qDebug() << items; // ["apple", "banana", "cherry"]

メリット
正規表現よりも記述が簡単で、処理が高速である可能性があります。 デメリット: 複雑なパターンマッチングには対応できません。

QRegularExpression クラス (Qt 5 以降)

Qt 5 以降では、より強力で柔軟な正規表現エンジンを提供する QRegularExpression クラスが導入されました。QRegularExpressionMatch クラスと組み合わせて使用し、キャプチャされたテキストへのアクセスもより洗練されています。

  • 名前付きキャプチャグループ
    (?<name>...) の形式でキャプチャグループに名前を付けることができ、インデックスではなく名前でアクセスできます。
  • QRegularExpressionMatch::capturedTexts()
    すべてのキャプチャされたテキストを QStringList として取得します(QRegExp::capturedTexts() と同様の機能)。
  • QRegularExpressionMatch::captured(int index) / QRegularExpressionMatch::captured(const QString &name)
    指定されたインデックスまたは名前のキャプチャグループにマッチしたテキストを取得します。
  • QRegularExpressionMatch::hasMatch()
    マッチが成功したか確認します。
  • QRegularExpression::match()
    文字列全体または部分文字列とのマッチを試みます。


QRegularExpression を使用してログエントリを解析する (例1の QRegExp の代替)

#include <QCoreApplication>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString logEntry = "[INFO] 2023-10-27 10:30:00 - Application started.";
    QRegularExpression rx("\\[(?<level>\\w+)\\] (?<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) - (?<message>.*)");
    QRegularExpressionMatch match = rx.match(logEntry);

    if (match.hasMatch()) {
        qDebug() << "ログエントリ全体:" << match.captured(0);
        qDebug() << "ログレベル:" << match.captured("level");
        qDebug() << "タイムスタンプ:" << match.captured("timestamp");
        qDebug() << "メッセージ:" << match.captured("message");
    } else {
        qDebug() << "ログエントリの形式が一致しませんでした。";
    }

    return a.exec();
}

メリット
より強力な正規表現エンジン、名前付きキャプチャグループ、より明確な API。 デメリット: Qt 4 以前のバージョンでは使用できません。

QTextStream と文字列操作

複雑なパターンマッチングではないものの、テキストファイルを読み込みながら特定の形式のデータを抽出するような場合には、QTextStreamQString の基本的な文字列操作を組み合わせることもできます。


CSV ファイルからデータを読み込む

#include <QCoreApplication>
#include <QFile>
#include <QTextStream>
#include <QStringList>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QFile file("data.csv");
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        while (!in.atEnd()) {
            QString line = in.readLine();
            QStringList fields = line.split(',');
            if (fields.size() == 3) {
                QString name = fields.at(0).trimmed();
                int age = fields.at(1).trimmed().toInt();
                QString city = fields.at(2).trimmed();
                qDebug() << "名前:" << name << ", 年齢:" << age << ", 都市:" << city;
            } else {
                qDebug() << "不正な形式の行:" << line;
            }
        }
        file.close();
    } else {
        qDebug() << "ファイルのオープンに失敗しました。";
    }

    return a.exec();
}

メリット
ファイル処理と簡単なデータ抽出を同時に行える。 デメリット: 複雑なパターンマッチングには不向き。

外部ライブラリの利用

より高度な正規表現機能や、特定の形式のテキスト解析に特化した外部ライブラリを利用することも考えられます。例えば、Boost.Regex などが挙げられます。ただし、Qt のエコシステム外のライブラリを利用する場合は、依存関係の管理やビルド設定などに注意が必要です。

  • 非常に高度な正規表現機能や特殊なテキスト処理
    外部ライブラリの利用を検討します。
  • ファイルからの簡単なデータ抽出
    QTextStreamQString::split() などを組み合わせるのが簡単な場合があります。
  • 複雑なパターンマッチング (Qt 4 以前)
    QRegExp を使用します。
  • 複雑なパターンマッチング (Qt 5 以降)
    QRegularExpression を推奨します。より強力で柔軟性があり、名前付きキャプチャグループなどの便利な機能があります。
  • 単純な文字列検索や抽出
    QString のメンバ関数で十分です。