Qt 正規表現の基本:QRegExp と cap() の関係

2025-03-21

キャプチャグループとは?

正規表現の中で、特定のパターンを括弧 () で囲むことで、その部分にマッチした文字列を「キャプチャ」することができます。これらの括弧で囲まれた部分は、それぞれ独立した「キャプチャグループ」として扱われます。

QRegExp::cap() の役割

QRegExp::cap() メソッドは、これらのキャプチャグループでキャプチャされた文字列にアクセスするために使用されます。

QRegExp::cap(int n) の使い方

QRegExp::cap() メソッドは、整数型の引数 n を受け取ります。この n は、取得したいキャプチャグループのインデックス番号を表します。

  • n > 0
    n 番目のキャプチャグループ(正規表現内で左から数えて n 番目の () で囲まれた部分)にマッチした文字列を返します。
  • n = 0
    正規表現全体にマッチした文字列を返します。

以下の例を考えてみましょう。

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

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

    QString text = "名前は太郎、年齢は30歳です。";
    QRegExp rx("名前は(.*)、年齢は(\\d+)歳です。");

    if (rx.indexIn(text) != -1) {
        qDebug() << "マッチしました!";
        qDebug() << "全体のマッチ:" << rx.cap(0); // "名前は太郎、年齢は30歳です。"
        qDebug() << "名前:" << rx.cap(1);       // "太郎"
        qDebug() << "年齢:" << rx.cap(2);       // "30"
    } else {
        qDebug() << "マッチしませんでした。";
    }

    return a.exec();
}

この例では、QRegExp オブジェクト rx は、名前と年齢を抽出するための正規表現 名前は(.*)、年齢は(\\d+)歳です。 を使用しています。

  • (\\d+) は、1つ以上の数字にマッチし、年齢をキャプチャグループ2としてキャプチャします。
  • (.*) は、任意の文字の0回以上の繰り返しにマッチし、名前をキャプチャグループ1としてキャプチャします。

rx.indexIn(text) がマッチに成功した場合、以下のように rx.cap() を使用して各グループの文字列を取得できます。

  • rx.cap(2) は、2番目のキャプチャグループ(年齢)にマッチした文字列("30")を返します。
  • rx.cap(1) は、最初のキャプチャグループ(名前)にマッチした文字列("太郎")を返します。
  • rx.cap(0) は、正規表現全体にマッチした文字列("名前は太郎、年齢は30歳です。")を返します。
  • キャプチャグループのインデックス番号は、正規表現内の左から右への括弧の順序に対応します。
  • キャプチャグループが存在しない場合(括弧 () が正規表現に含まれていない場合)、cap(1) などを呼び出しても空の文字列が返されます。
  • QRegExp::cap() を呼び出す前に、QRegExp::indexIn()QRegExp::globalMatch() などを使用して、正規表現が文字列にマッチしていることを確認する必要があります。マッチしていない状態で cap() を呼び出すと、空の文字列が返されるか、予期しない結果になる可能性があります。


マッチングに失敗しているのに cap() を呼び出している

エラー内容
正規表現が対象の文字列にマッチしていないにもかかわらず、QRegExp::cap() を呼び出してしまい、空の文字列や予期しない結果が得られる。

原因
QRegExp::indexIn()QRegExp::globalMatch() などのマッチング関数が false (-1 や空のリスト) を返しているにもかかわらず、マッチングが成功したと誤って判断している。

トラブルシューティング

  • 必ずマッチングを確認する
    QRegExp::cap() を呼び出す前に、必ず QRegExp::indexIn() の戻り値が -1 でないこと、または QRegExp::globalMatch() の結果が空でないことを確認してください。
    QRegExp rx("パターン");
    QString text = "テスト文字列";
    if (rx.indexIn(text) != -1) {
        qDebug() << "マッチしました!";
        qDebug() << "キャプチャグループ1:" << rx.cap(1);
    } else {
        qDebug() << "マッチしませんでした。";
    }
    

キャプチャグループのインデックス番号を間違えている

エラー内容
取得したいキャプチャグループのインデックス番号を間違えて指定し、意図した文字列が取得できない。

原因
正規表現内のキャプチャグループの順序(左から右への括弧の出現順)を理解していない。

トラブルシューティング

  • テスト用の正規表現チェッカーを使用する
    オンラインの正規表現チェッカーや Qt Creator の正規表現テスターなどを使用して、自分の正規表現が意図した通りにキャプチャグループを認識しているかを確認すると便利です。
  • 正規表現をよく確認する
    正規表現のどの部分がどのキャプチャグループに対応しているかを注意深く確認してください。括弧 () で囲まれた部分がキャプチャグループであり、左から順にインデックス番号が割り当てられます。

キャプチャグループが存在しない

エラー内容
正規表現にキャプチャグループ(括弧 () で囲まれた部分)が含まれていないか、またはマッチングしたパターンがキャプチャグループを持たない場合に cap() を呼び出し、空の文字列が返される。

原因
期待されるキャプチャグループが正規表現に定義されていない。

トラブルシューティング

  • キャプチャグループの有無を確認する
    マッチングが成功したとしても、キャプチャグループが定義されていなければ cap() は空の文字列を返します。正規表現の構造を再確認してください。
  • 正規表現を見直す
    必要な情報をキャプチャするために、正規表現に適切なキャプチャグループを追加しているか確認してください。

グローバルマッチングで cap() を使用する際の誤解

エラー内容
QRegExp::globalMatch() を使用して複数のマッチングを処理している際に、各マッチングの結果に対して cap() を呼び出す際に混乱が生じる。

原因
globalMatch() はマッチングが見つかるたびに内部的に位置を進めますが、cap() は最後に成功したマッチングの結果を参照します。

トラブルシューティング

  • 各マッチングの後に cap() を使用する
    globalMatch()true を返すたびに、その時点での QRegExp オブジェクトが保持しているマッチング情報に対して cap() を呼び出します。
    QRegExp rx("(\\w+)");
    QString text = "one two three";
    int pos = 0;
    while ((pos = rx.indexIn(text, pos)) != -1) {
        qDebug() << "マッチ:" << rx.cap(1) << " at position " << pos;
        pos += rx.matchedLength();
    }
    
    globalMatch() を使用する場合は、同様のループ処理を行い、各マッチングの結果に対して cap() を使用します。

正規表現自体の問題

エラー内容
正規表現の構文に誤りがあり、意図したマッチングが行われない。

原因
正規表現の構文エラーや、パターンが対象の文字列に合致しない。

トラブルシューティング

  • Qt Creator の正規表現テスターを使用する
    Qt Creator には正規表現テスターが組み込まれており、実際の文字列と正規表現を試すことができます。これを利用して、自分の正規表現が意図した通りに動作するかを確認できます。
  • より単純な正規表現でテストする
    まずはより単純な正規表現でテストし、問題が正規表現自体にあるのか、cap() の使い方にあるのかを切り分けてください。
  • 正規表現の構文を確認する
    正規表現の構文が正しいか、特殊文字のエスケープなどが正しく行われているかを確認してください。

大文字・小文字の区別

エラー内容
正規表現で大文字と小文字を区別する設定になっており、意図しないマッチング結果になる。

原因
デフォルトでは QRegExp は大文字と小文字を区別します。

  • 大文字・小文字を区別しない設定
    大文字と小文字を区別しないようにしたい場合は、QRegExp::setCaseSensitivity(Qt::CaseInsensitive) を使用します。
    QRegExp rx("pattern", Qt::CaseInsensitive);
    


例1:基本的なキャプチャグループからの文字列取得

この例では、名前と年齢を含む文字列から、それぞれの情報を正規表現を使って抽出します。

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

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

    QString text = "名前は山田太郎、年齢は35歳です。";
    QRegExp rx("名前は(.*)、年齢は(\\d+)歳です。");

    if (rx.indexIn(text) != -1) {
        qDebug() << "マッチしました!";
        qDebug() << "名前:" << rx.cap(1); // 最初のキャプチャグループ(名前)
        qDebug() << "年齢:" << rx.cap(2); // 2番目のキャプチャグループ(年齢)
    } else {
        qDebug() << "マッチしませんでした。";
    }

    return a.exec();
}

解説

  1. QString text = "名前は山田太郎、年齢は35歳です。";: 処理対象の文字列を定義します。
  2. QRegExp rx("名前は(.*)、年齢は(\\d+)歳です。");: 正規表現オブジェクト rx を作成します。
    • 名前は、年齢は はそのままのマッチングを要求します。
    • (.*) は、任意の文字の0回以上の繰り返しにマッチし、最初のキャプチャグループとして名前を捉えます。
    • (\\d+) は、1つ以上の数字にマッチし、2番目のキャプチャグループとして年齢を捉えます。
    • 歳です。 はそのままのマッチングを要求します。
  3. if (rx.indexIn(text) != -1): indexIn() メソッドを使用して、正規表現が text にマッチするかどうかを確認します。マッチすれば -1 以外(マッチした開始位置)が返されます。
  4. qDebug() << "名前:" << rx.cap(1);: マッチした場合、rx.cap(1) を使用して、最初のキャプチャグループ(名前)にマッチした文字列を取得し、コンソールに出力します。
  5. qDebug() << "年齢:" << rx.cap(2);: 同様に、rx.cap(2) を使用して、2番目のキャプチャグループ(年齢)にマッチした文字列を取得し、コンソールに出力します。

例2:複数のマッチングと cap() の使用

この例では、文字列の中から複数の数字の塊を抽出し、それぞれの数字を取得します。

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

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

    QString text = "価格は123円、数量は5個、合計は615円です。";
    QRegExp rx("(\\d+)"); // 1つ以上の数字にマッチする正規表現

    int pos = 0;
    while ((pos = rx.indexIn(text, pos)) != -1) {
        qDebug() << "数字が見つかりました:" << rx.cap(1) << " at position " << pos;
        pos += rx.matchedLength(); // 次のマッチング位置を更新
    }

    return a.exec();
}

解説

  1. QString text = "価格は123円、数量は5個、合計は615円です。";: 処理対象の文字列です。
  2. QRegExp rx("(\\d+)");: 1つ以上の数字 (\d+) をキャプチャグループとして捉える正規表現です。
  3. int pos = 0;: 検索開始位置を初期化します。
  4. while ((pos = rx.indexIn(text, pos)) != -1): indexIn() をループ内で使用し、pos から順にマッチングを試みます。マッチが見つかると、マッチした開始位置が pos に代入され、ループが継続します。
  5. qDebug() << "数字が見つかりました:" << rx.cap(1) << " at position " << pos;: マッチが見つかるたびに、rx.cap(1) でその数字を取得し、位置とともにコンソールに出力します。
  6. pos += rx.matchedLength();: 次のマッチングを試すために、現在のマッチングの長さを pos に加算して、検索位置を更新します。

例3:特定の形式のデータからの情報抽出

この例では、メールアドレスのような特定の形式の文字列から、ユーザー名とドメインを抽出します。

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

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

    QString email = "[email protected]";
    QRegExp rx("([\\w.]+?)@([\\w.-]+)");

    if (rx.indexIn(email) != -1) {
        qDebug() << "メールアドレスを解析しました:";
        qDebug() << "ユーザー名:" << rx.cap(1);
        qDebug() << "ドメイン:" << rx.cap(2);
    } else {
        qDebug() << "メールアドレスの形式が正しくありません。";
    }

    return a.exec();
}
  1. QString email = "[email protected]";: 解析対象のメールアドレスです。
  2. QRegExp rx("([\\w.]+?)@([\\w.-]+)");: メールアドレスの形式に合わせた正規表現です。
    • ([\\w.]+?): ユーザー名部分。\\w は英数字とアンダースコア、. はドットにマッチします。+? は、1回以上マッチし、非貪欲(最短)マッチングを行います。これが最初のキャプチャグループです。
    • @: アットマークにマッチします。
    • ([\\w.-]+): ドメイン部分。\\w は英数字とアンダースコア、. はドット、- はハイフンにマッチします。+ は1回以上マッチします。これが2番目のキャプチャグループです。
  3. if (rx.indexIn(email) != -1): メールアドレスにマッチするかどうかを確認します。
  4. qDebug() << "ユーザー名:" << rx.cap(1);: 最初のキャプチャグループ(ユーザー名)を取得します。
  5. qDebug() << "ドメイン:" << rx.cap(2);: 2番目のキャプチャグループ(ドメイン)を取得します。


QRegularExpression クラスの使用

Qt 5.0 以降では、より高性能で標準的な正規表現エンジンを提供する QRegularExpression クラスが導入されました。QRegularExpression は、QRegExp よりも多くの機能を提供し、パフォーマンスも向上している場合があります。

代替方法

QRegularExpression を使用する場合、キャプチャグループへのアクセスは match() メソッドの結果である QRegularExpressionMatch オブジェクトを通じて行います。

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

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

    QString text = "名前は山田太郎、年齢は35歳です。";
    QRegularExpression rx("名前は(.*)、年齢は(\\d+)歳です。");

    QRegularExpressionMatch match = rx.match(text);
    if (match.hasMatch()) {
        qDebug() << "マッチしました!";
        qDebug() << "名前:" << match.captured(1); // キャプチャグループ1
        qDebug() << "年齢:" << match.captured(2); // キャプチャグループ2
    } else {
        qDebug() << "マッチしませんでした。";
    }

    return a.exec();
}

特徴

  • 名前付きキャプチャグループ
    QRegularExpression では、キャプチャグループに名前を付けることができ、match.captured("groupName") のように名前でアクセスできます。
  • パフォーマンスの向上
    一般的に QRegularExpression の方が QRegExp よりも高速に動作することがあります。
  • よりモダンな API
    QRegularExpression はより直感的で、現代的な正規表現の機能(例: 非貪欲マッチング、名前付きキャプチャグループなど)をより直接的にサポートしています。

文字列操作関数 (QString::split(), QString::mid(), QString::indexOf() など) の活用

正規表現を使用せずに、QString クラスの基本的な文字列操作関数を組み合わせて目的の情報を抽出することも可能です。特に、特定の区切り文字や固定のパターンを持つ文字列の場合に有効です。

代替方法

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

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

    QString text = "名前=山田太郎,年齢=35";
    QStringList parts = text.split(',');
    QString name = "";
    QString age = "";

    foreach (const QString &part, parts) {
        if (part.startsWith("名前=")) {
            name = part.mid(5);
        } else if (part.startsWith("年齢=")) {
            age = part.mid(5);
        }
    }

    if (!name.isEmpty() && !age.isEmpty()) {
        qDebug() << "名前:" << name;
        qDebug() << "年齢:" << age;
    } else {
        qDebug() << "情報が見つかりませんでした。";
    }

    return a.exec();
}

特徴

  • 複雑なパターンには不向き
    複雑なパターンや柔軟なマッチングが必要な場合は、コードが複雑になり、メンテナンス性が低下する可能性があります。
  • オーバーヘッドが少ない
    正規表現エンジンの起動や解析のオーバーヘッドがないため、単純な文字列操作では高速な場合があります。
  • シンプルで理解しやすい
    特定のパターンが固定されている場合、正規表現よりも直感的で理解しやすいコードになることがあります。

XML や JSON などの構造化されたデータ形式の利用

抽出したい情報が構造化されたデータとして存在する場合(例: XML や JSON)、専用のパーサーライブラリを使用して情報を取得する方が一般的で、より堅牢な方法です。

代替方法

Qt には、XML を扱うための QXmlStreamReaderQDomDocument、JSON を扱うための QJsonDocument などのクラスが用意されています。

特徴

  • 正規表現よりも複雑な処理が必要
    構造化されたデータを扱うための知識と、専用のパーサーライブラリの利用方法を理解する必要があります。
  • データ形式の変更に強い
    データ形式が変更されても、パーサーの変更だけで対応できる場合があります。
  • 構造化されたデータの処理に特化
    データ構造を理解しやすく、エラー処理も容易です。

手動での文字列解析と状態管理

非常に特殊なパターンや、正規表現では表現が難しい複雑なルールを持つ文字列の場合、手動で文字列を走査し、状態を管理しながら情報を抽出する方法も考えられます。

代替方法

文字列を文字単位で処理したり、特定のフラグや状態変数を用いて解析を進めます。

特徴

  • メンテナンスが困難
    コードの理解と変更が難しくなることがあります。
  • 実装が複雑になりやすい
    コードが複雑になり、バグが発生しやすくなる可能性があります。
  • 非常に柔軟
    どのような複雑なパターンにも対応できます。
  • 非常に特殊なケース
    手動での解析も考慮する必要があるかもしれません。
  • 構造化されたデータ
    XML や JSON などの形式であれば、専用のパーサーを使用すべきです。
  • 複雑なパターンや柔軟なマッチングが必要
    QRegularExpression を使用するのが推奨されます。
  • 単純なパターンや固定の区切り文字
    QString の文字列操作関数が最も手軽で効率的な場合があります。