Mehrere Treffer

Möchte man mit einer Regular Expression mehrere Treffer aus einem Text filtern, stehen in C++ verschiedene Möglichkeiten für verschiedene Anwendungsfälle zur Verfügung.

Video

Quelltext

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    regex rex(R"(\d+)");
    string text { "Ein Text, in dem sich 3 Zahlen verstecken: "
                  "eine 23 hier und natürlich die Antwort auf alles: 42" };

    smatch sm;
    string tmp { text };
    while (regex_search(tmp, sm, rex)) {
        cout << "regex_search: " << sm.str() << endl;
        tmp = sm.suffix();
    }
    
    regex_iterator<string::iterator> regex_end;
    for (regex_iterator<string::iterator> regex_it(text.begin(), text.end(), rex); 
         regex_it != regex_end; 
         ++regex_it) {
        cout << "iterator: " << regex_it->str() << endl;
    }

    string csv { "Ein Feld   ,   in welchem,\t viele Teile,stecken" };
    regex separators(R"(\s*,\s*)");
    regex_token_iterator<string::iterator> regex_token_end;
    cout << csv << endl;
    for (regex_token_iterator<string::iterator> regex_it(csv.begin(), 
                                                         csv.end(), 
                                                         separators, { -1 }); 
         regex_it != regex_token_end; ++regex_it) {
        cout << "Felder: " << *regex_it << endl;
    }
}

Erklärung

std::regex_search sucht grundsätzlicher erstmal immer nur nach dem ersten Treffer in einem Text. Will man also weitere Treffer finden, muss man den Text entsprechend abschneiden und sich so von Treffer zu Treffer hangeln. Glücklicherweise ist das so vorgesehen und das std::match_result-Template stellt eine entsprechende Methode zur Verfügung. Mit match_result::suffix() erhält man den String hinter dem aktuellen Treffer. So kann man sich mit einer temporären Variable (siehe Zeile 17) weiterhangeln und durch wiederholten Aufruf von regex_search alle Treffer finden. Der Rückgabewert von regex_search zeigt an, ob noch ein Treffer gefunden wurde. Ist er true, dann wurde noch was gefunden (und der Inhalt von match_result ist gültig), ansonsten kann man abbrechen.

Etwas einfacher und ohne Modifikation eines temporären Strings geht es mit std::regex_iterator. Diese Klasse stellt einen ForwardIterator zur Verfügung, mit dem man nach und nach die Ergebnisse der Suche aufzählen lassen kann. Der Iterator wird mit dem Anfang und Ende des Zeichenbereiches und dem zu durchsuchenden String initialisiert und kann dann wie üblich mit operator++ hochgezählt werden. Da die Klasse mit einem beliebigen Iterator-Typ als Quelle arbeiten kann, kommen nicht nur Strings in Frage, sondern im Prinzip jeder BidirectionalIterator (bspw. std::vector::iterator oder ähnliches). Als End-Iterator dient hier der mit dem Standardkonstruktor initialisierte regex_iterator.

Will man nicht einfach alle Suchergebnisse aufzählen, kann std::regex_token_iterator weiterhelfen. Dieser kann anhand einer Liste von submatch-Indexen eine Vorauswahl der zu liefernden Ergebnisse treffen. Enthält diese Liste eine -1, so können sogar alle "Nicht-Treffer", also alle Textteile zwischen den Treffern zurückgeliefert werden. Hier im Beispiel wird das genutzt, um einen Feldtrenner als Regex zu definieren und dann die Inhalte der Felder zu erhalten.