Qtの基礎 - 正規表現

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

Qtでは、QRegularExpressionクラスを使用して正規表現を扱う。
これは現代的な正規表現エンジンを提供しており、Perl互換の正規表現構文をサポートしている。

Qt 5.11以前まで使用されていたQRegExpクラスは現在非推奨となっている。

基本的な使用方法としては、パターンマッチングとテキスト置換が主な用途である。

パターンマッチングでは、文字列が特定のパターンに一致するかどうかを確認する。
例えば、Eメールアドレスの検証やURLの抽出等に活用できる。

QRegularExpressionクラスには、大文字小文字を区別するオプション複数行モードの有効化等、様々なオプションを設定することができる。
これらのオプションは、パターンマッチングの動作を細かく制御するのに役立つ。

マッチング結果はQRegularExpressionMatchオブジェクトとして返されて、マッチした部分文字列や位置情報等の詳細な情報を取得できる。
また、グループキャプチャを使用する場合は、パターン内の特定の部分にマッチした文字列を個別に取得することも可能である。

正規表現のパターンが正しく構築されているかどうかを確認する場合は、isValidメソッドで事前に確認できる。
これは、実行時エラーを防ぐために重要である。

パフォーマンスにおいて、同じパターンを複数回使用する場合は、QRegularExpressionオブジェクトを再利用することを推奨する。
これにより、パターンの再コンパイルを避けることができる。


QRegularExpressionクラス (推奨)

QRegularExpressionクラスとは

QRegularExpressionクラスは、PCREライブラリを基盤としており、以下に示すようなメリットがある。

  • パフォーマンスの向上
内部実装が最適化されており、特に複雑なパターンマッチングでより高速に動作する。
  • エラーハンドリングの改善
    マッチング結果やエラー状態をより詳細に把握できるようになった。
    以前のQRegExpクラスでは、エラーが発生した際の情報が限定的であった。
  • Unicode対応の強化
    現代的なテキスト処理に必要な、より包括的なUnicodeサポートを提供している。


QRegularExpressionクラスを使用することにより、コードの保守性が向上して、より堅牢なテキスト処理機能を実装できる。
移行を検討する場合は、既存のQRegExpクラスを使用したコードにおいて、新しい機能の実装時やコードのメンテナンス時に徐々にQRegularExpressionクラスへ移行することが推奨される。

QRegularExpressionクラスで頻繁に使用するメソッドを以下に示す。

  • hasMatchメソッド
    パターンがテキストにマッチしたかどうかをbool値で返す。
    trueの場合はマッチが成功、falseの場合は失敗を意味する。
  • capturedメソッド
    マッチした部分文字列や、括弧でグループ化された部分を取得する。
    引数無しの場合は完全なマッチを返す。
    数値を指定する場合、その番号のキャプチャグループを返す。
    文字列を指定する場合、その名前のキャプチャグループを返す。


※注意

  • パターンの最適化
    頻繁に使用する正規表現パターンは、オブジェクトとして保持して再利用することにより、パフォーマンスを向上させることができる。
  • エラー処理
    isValidメソッドを使用して、正規表現パターンの妥当性を確認することが推奨される。


QRegularExpressionクラスの使用例

 #include <QRegularExpression>
 #include <QDebug>
 
 class RegexExamples
 {
 public:
    // メールアドレスの検証
    static bool validateEmail(const QString& email)
    {
       // CaseInsensitiveOptionオプションは、大文字と小文字を区別せずにマッチングを行う
       QRegularExpression re("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$", QRegularExpression::CaseInsensitiveOption);
       QRegularExpressionMatch match = re.match(email);
       return match.hasMatch();
    }
 
    // 電話番号からハイフンを除去
    static QString normalizePhoneNumber(const QString& phone)
    {
       QRegularExpression re("[^0-9]");
       return phone.replace(re, "");
    }
 
    // HTMLタグの抽出
    static void extractHtmlTags(const QString& html)
    {
       QRegularExpression re("<([a-zA-Z0-9]+)[^>]*>");
       QRegularExpressionMatchIterator i = re.globalMatch(html);
 
       while (i.hasNext()) {
          QRegularExpressionMatch match = i.next();
          qDebug() << "Found tag:" << match.captured(1);
       }
    }
 
    // 日付形式の変換 (YYYY/MM/DD -> MM-DD-YYYY)
    static QString convertDateFormat(const QString& date)
    {
       QRegularExpression re("(\\d{4})/(\\d{2})/(\\d{2})");
       QRegularExpressionMatch match = re.match(date);
 
       if (match.hasMatch()) {
          return QString("%1-%2-%3").arg(match.captured(2))
                                    .arg(match.captured(3))
                                    .arg(match.captured(1));
       }
       return date;
    }
 
    // 文字列内の数値を抽出して合計を計算
    static int sumNumbersInText(const QString& text)
    {
       QRegularExpression re("\\d+");
       QRegularExpressionMatchIterator i = re.globalMatch(text);
       int sum = 0;
 
       while (i.hasNext()) {
          QRegularExpressionMatch match = i.next();
          sum += match.captured().toInt();
       }
       return sum;
    }
 
    // URLからクエリパラメータを抽出
    static QMap<QString, QString> parseQueryParameters(const QString& url)
    {
       QMap<QString, QString> params;
       QRegularExpression re("([^=&?]+)=([^&]*)");
       QRegularExpressionMatchIterator i = re.globalMatch(url);
 
       while (i.hasNext()) {
          QRegularExpressionMatch match = i.next();
          params[match.captured(1)] = match.captured(2);
       }
       return params;
    }
 
    // コードからコメントを抽出
    static void extractComments(const QString& code)
    {
       // 単一行コメント
       // MultilineOptionオプションは、複数行のテキストで、行ごとに独立してパターンマッチングを行う
       // ^ (行頭) と $ (行末) が各行に対して機能する
       QRegularExpression singleLine("//(.*)$", QRegularExpression::MultilineOption);
 
       // 複数行コメント
       QRegularExpression multiLine("/\\*([^*]|\\*(?!/))*\\*/");
 
       auto processMatch = [](const QRegularExpressionMatch& match) {
          QString comment = match.captured(0).trimmed();
          qDebug() << "Found comment:" << comment;
       };
 
       // globalMatchメソッドを使用して、テキスト内の全てのマッチを処理
       QRegularExpressionMatchIterator i1 = singleLine.globalMatch(code);
       while (i1.hasNext()) {
          processMatch(i1.next());
       }
 
       QRegularExpressionMatchIterator i2 = multiLine.globalMatch(code);
       while (i2.hasNext()) {
          processMatch(i2.next());
       }
    }
 };


 // 使用例
 
 #include <QDebug>
 
 qDebug() << "Email validation: " << RegexExamples::validateEmail("test@example.com");
 
 qDebug() << "Normalized phone: " << RegexExamples::normalizePhoneNumber("123-456-7890");
 
 QString html = "<div>content</div><p>paragraph</p>";
 RegexExamples::extractHtmlTags(html);
 
 qDebug() << "Converted date: " << RegexExamples::convertDateFormat("2024/03/25");
 
 QString text = "Values: 10, 20, 30";
 qDebug() << "Sum of numbers: " << RegexExamples::sumNumbersInText(text);
 
 QString url = "https://example.com?name=John&age=25";
 QMap<QString, QString> params = RegexExamples::parseQueryParameters(url);
 qDebug() << "URL parameters:" << params;
 
 QString code = "// This is a comment\nint x = 5;\n/* Multi\nline\ncomment */";
 RegexExamples::extractComments(code);


CaseInsensitiveOptionオプション

 #include <QRegularExpression>
 #include <QDebug>
 
 QRegularExpression reCase("hello", QRegularExpression::CaseInsensitiveOption);
 QRegularExpressionMatch matchCase = reCase.match("HELLO world");
 qDebug() << "Case insensitive match: " << matchCase.hasMatch();  // true


MultilineOptionオプション

 #include <QRegularExpression>
 #include <QDebug>
 
 QString multilineText = "Line1\nLine2\nLine3";
 QRegularExpression reMulti("^Line", QRegularExpression::MultilineOption);
 QRegularExpressionMatchIterator i = reMulti.globalMatch(multilineText);
 while (i.hasNext()) {
    QRegularExpressionMatch match = i.next();
    qDebug() << "Found at position: " << match.capturedStart();
 }


キャプチャグループ (数値指定)

 #include <QRegularExpression>
 #include <QDebug>
 
 QRegularExpression reGroup("(\\w+)=(\\d+)");
 QRegularExpressionMatch matchGroup = reGroup.match("age=25");
 if (matchGroup.hasMatch()) {
    qDebug() << "Full match: " << matchGroup.captured(0);    // "age=25"
    qDebug() << "First group: " << matchGroup.captured(1);   // "age"
    qDebug() << "Second group: " << matchGroup.captured(2);  // "25"
 }


名前付きキャプチャグループ

 #include <QRegularExpression>
 #include <QDebug>
 
 QRegularExpression reNamed("(?<key>\\w+)=(?<value>\\d+)");
 QRegularExpressionMatch matchNamed = reNamed.match("count=42");
 if (matchNamed.hasMatch()) {
    qDebug() << "Key: " << matchNamed.captured("key");     // "count"
    qDebug() << "Value: " << matchNamed.captured("value"); // "42"
 }


マッチ位置の取得

 #include <QRegularExpression>
 #include <QDebug>
 
 QString text = "Hello, my email is test@example.com and phone is 123-456-7890";
 
 // メールアドレスのパターン
 QRegularExpression emailRegex(R"(\b[\w\.-]+@[\w\.-]+\.\w+\b)");
 
 // メールアドレスのマッチング
 QRegularExpressionMatch emailMatch = emailRegex.match(text);
 if (emailMatch.hasMatch()) {
    qDebug() << "Match text: " << emailMatch.captured(0);
    qDebug() << "Start position: " << emailMatch.capturedStart();
    qDebug() << "Length: " << emailMatch.capturedLength();
    qDebug() << "End position: " << emailMatch.capturedStart() + emailMatch.capturedLength();
 }
 
 // 電話番号のパターン
 QRegularExpression phoneRegex(R"(\d{3}-\d{3}-\d{4})");
 
 // 電話番号のマッチング
 QRegularExpressionMatch phoneMatch = phoneRegex.match(text);
 if (phoneMatch.hasMatch()) {
    qDebug() << "Match text: " << phoneMatch.captured(0);
    qDebug() << "Start position: " << phoneMatch.capturedStart();
    qDebug() << "Length: " << phoneMatch.capturedLength();
    qDebug() << "End position: " << phoneMatch.capturedStart() + phoneMatch.capturedLength();
 }



QRegExpクラス (非推奨)

QRegExpクラスには技術的な限界があり、PCRE (Perl Compatible Regular Expressions) との完全な互換性がなく、現代的な正規表現機能の一部をサポートしていない。
例えば、先読み・後読みのような高度なパターンマッチング機能が制限されている。

Qt 5.12以降では、QRegExpクラスの代わりにQRegularExpressionクラスの使用が推奨されている。

※注意
パターンに特殊文字を含める場合は、適切にエスケープする必要がある。
パフォーマンスが重要な場合は、正規表現パターンを事前にコンパイルしておくことを推奨する。

基本的なマッチング

 // 単純な文字列パターンのマッチング
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx1("hello");
 bool match = rx1.exactMatch("hello");  // true
 qDebug() << "Basic match:" << match;


メタキャラクターを使用したパターン

 // \d (数字) や \w (単語文字) 等の特殊文字の使用
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx2("\\d{3}-\\d{4}");  // 郵便番号パターン
 qDebug() << "Postal code match:" << rx2.exactMatch("123-4567");  // true


選択パターン

 // | (パイプ) を使用した複数パターンの選択
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx3("cat|dog");
 qDebug() << "Alternative match:" << rx3.exactMatch("cat");  // true
 qDebug() << "Alternative match:" << rx3.exactMatch("dog");  // true


グループ化とキャプチャ

 // ()を使用したグループ化と、capメソッドによるキャプチャ内容の取得
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx4("(\\w+)@(\\w+\\.\\w+)");
 QString text = "contact@example.com";
 if (rx4.indexIn(text) != -1) {
    QString username = rx4.cap(1);  // "contact"
    QString domain = rx4.cap(2);    // "example.com"
    qDebug() << "Username:" << username << "Domain:" << domain;
 }


量指定子の使用

 // {n,m}形式での繰り返し回数の指定
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx5("a{2,4}");  // aが2回から4回
 qDebug() << "Quantifier match:" << rx5.exactMatch("aaa");  // true


文字クラス

 // []を使用した文字の範囲指定
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx6("[A-Za-z0-9]+");  // 英数字の1回以上の繰り返し
 qDebug() << "Character class match:" << rx6.exactMatch("Test123");  // true


テキスト置換

 // replaceメソッドを使用したパターンマッチング置換
 
 #include <QRegExp>
 #include <QDebug>
 
 QString text2 = "The color is gray.";
 QRegExp rx7("gray");
 text2.replace(rx7, "blue");
 qDebug() << "Replaced text:" << text2;  // "The color is blue."


否定文字クラス

 // [^...]形式での否定パターン
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx8("[^0-9]+");  // 数字以外の文字の1回以上の繰り返し
 qDebug() << "Negated class match:" << rx8.exactMatch("ABC");  // true


位置指定

 // ^ (行頭) や $ (行末) 等のアンカー
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx9("^Start");  // 行頭にStartがあるかチェック
 qDebug() << "Position match:" << rx9.exactMatch("Start of line");  // true


グリーディマッチとノングリーディマッチ

 // .と.?の違いによる最長一致と最短一致の制御
 
 #include <QRegExp>
 #include <QDebug>
 
 QString text3 = "<p>First</p><p>Second</p>";
 QRegExp rx10("<p>.*</p>");     // グリーディマッチ
 QRegExp rx11("<p>.*?</p>");    // ノングリーディマッチ
 
 if (rx10.indexIn(text3) != -1) {
    qDebug() << "Greedy match:" << rx10.cap(0);    // "<p>First</p><p>Second</p>"
 }
 
 if (rx11.indexIn(text3) != -1) {
    qDebug() << "Non-greedy match:" << rx11.cap(0); // "<p>First</p>"
 }


ワイルドカードモード

 // ファイル名パターンマッチング等に使用する簡易的なパターン
 
 #include <QRegExp>
 #include <QDebug>
 
 QRegExp rx12("doc*.txt", Qt::CaseSensitive, QRegExp::Wildcard);
 qDebug() << "Wildcard match:" << rx12.exactMatch("document.txt");  // true



エラー

Qt 6において、QRegExpクラスを使用する場合、以下に示すエラーが発生する。

QRegExp was not declared in this scope


このエラーは、Qt 6ではQRegExpクラスが非推奨となり、QRegularExpressionクラスを使用する必要がある。

 #include <QRegularExpression>
 
 static QRegularExpression RegEx("[+-]");
 
 QString coordinate = "111-222+333";
 QStringList parts = coordinate.split(RegEx, Qt::SkipEmptyParts);


QRegularExpressionクラスは、より強力で効率的な正規表現エンジンを提供しており、QRegExpクラスと比較していくつかの利点がある。