Ранее мы уже затрагивали тему регулярных выражений в Qt на примерах использования QRegExp. В этот раз рассмотрим класс QRegularExpression, добавленный в Qt5. Пройдемся по основным вариантам его использования.
QRegularExpression: Проверка на совпадение
Определим список строк, для которых будем проводить тесты:
1 2 3 4 5 6 7 8 9 |
static const QStringList STRINGS = { "#include <iostream>", "before\n#include <iostream>\nafter", " #include <iostream> ", "#include <", " #include <iostream> ", "#include \"iostream\"", "#define <iostream>" }; |
Сами тесты по проверке на совпадение будут однотипными. Разница заключается лишь в параметрах, с которыми был инициализирован экземпляр QRegularExpression. Поэтому создадим соответствующую вспомогательную функцию:
1 2 3 4 5 6 |
void runMatchingTest( const QRegularExpression& re ) { for( const auto& str : STRINGS ) { auto match = re.match( str ); qDebug() << ( match.hasMatch() ? " matched" : "mismatched" ) << ":" << str; } } |
Функция runMatchingTest() для каждой строки из списка вызывает re.match(), которая возвращает объект класса QRegularExpressionMatch. Он содержит довольно много полезной информации, но для нашей задачи достаточно вызвать match.hasMatch(). Если совпадение найдено, то возвращается true, иначе — false.
Простое совпадение
Начинаем с поведения по умолчанию:
1 2 3 4 |
void normalMatchingTest() { QRegularExpression re( "#include <[^>]+>" ); runMatchingTest( re ); } |
Результат выполнения предсказуем:
1 2 3 4 5 6 7 |
matched : "#include <iostream>" matched : "before\n#include <iostream>\nafter" matched : " #include <iostream> " mismatched : "#include <" mismatched : " #include <iostream> " mismatched : "#include \"iostream\"" mismatched : "#define <iostream>" |
Обратите внимание, что совпала не только строка, которая однозначно соответствует регулярному выражению. Сюда же попали и те строки, которые просто содержат проверяемое выражение в качестве подстроки.
Точное совпадение
Отфильтровать лишние строки для получения точного совпадения не так уж сложно:
1 2 3 4 |
void exactMatchingTest() { QRegularExpression re( "^#include <[^>]+>$" ); runMatchingTest( re ); } |
Мы лишь добавили в регулярное выражение явное указание начала ( ^) и конца ( $) строки. На консоль будет выведено:
1 2 3 4 5 6 7 |
matched : "#include <iostream>" mismatched : "before\n#include <iostream>\nafter" mismatched : " #include <iostream> " mismatched : "#include <" mismatched : " #include <iostream> " mismatched : "#include \"iostream\"" mismatched : "#define <iostream>" |
Получили то, что просили. Но возможно, нас интересуют не только строки, которые содержат лишь искомый шаблон. Могут понадобиться фрагменты многострочного текста, у которого хотя бы одна строка удовлетворяет регулярному выражению. Речь идет про вторую строку в списке: "before\n#include <iostream>\nafter".
Точное многострочное совпадение
Для QRegularExpression предусмотрены модификаторы поведения. Одним из таких модификаторов мы воспользуемся:
1 2 3 4 |
void multilneExactMatchingTest() { QRegularExpression re( "^#include <[^>]+>$", QRegularExpression::MultilineOption ); runMatchingTest( re ); } |
Модификатор QRegularExpression::MultilineOption заставляет воспринимать символы перехода на новую строку в качестве признаков начала и конца строки: ^, $. Поэтому теперь имеем такие результаты:
1 2 3 4 5 6 7 |
matched : "#include <iostream>" matched : "before\n#include <iostream>\nafter" mismatched : " #include <iostream> " mismatched : "#include <" mismatched : " #include <iostream> " mismatched : "#include \"iostream\"" mismatched : "#define <iostream>" |
Частичное совпадение
Иногда достаточно узнать, что строка не противоречит регулярному выражению, но при этом может ему не соответствовать полностью. Например, это актуально при валидации пользовательских данных с мгновенным откликом. Естественно, пока строка не набрана до конца, скорее всего, шаблон регулярного выражения будет нарушен. Поэтому указывать нужно только на заведомо некорректный ввод.
Добиться описанного выше поведения можно следующим образом:
1 2 3 4 5 6 7 8 |
void partialMatchingTest() { QRegularExpression re( "^#include <[^>]+>$" ); for( const auto& str : STRINGS ) { auto match = re.match( str, 0, QRegularExpression::PartialPreferCompleteMatch ); qDebug() << ( match.hasPartialMatch() ? "partially matched" : " mismatched" ) << ":" << str; } } |
Экземпляр QRegularExpression совпадает с регулярным выражением из подраздела про точное совпадение. Отличие наблюдается ниже. При вызове re.match() передается не только строка, но и два дополнительных параметра. Первый нам не особо нужен. Он лишь указывает на сдвиг от начала строки, откуда следует начинать поиск. Второй же явно включает режим «частичного совпадения».
Чтобы проверить, что получено частичное совпадение, вызывается match.hasPartialMatch(). Она возвращает true, если частичное совпадение имеется, и false в противном случае. Получаем следующий результат:
1 2 3 4 5 6 7 |
mismatched : "#include <iostream>" mismatched : "before\n#include <iostream>\nafter" mismatched : " #include <iostream> " partially matched : "#include <" mismatched : " #include <iostream> " mismatched : "#include \"iostream\"" mismatched : "#define <iostream>" |
Частичное совпадение выполнено для строки "#include <". Все правильно. Но по какой-то причине была пропущена строка "#include <iostream>". И такое поведение вполне корректно. Для этой строки совпадение полное, а не частичное, поэтому true вернет не match.hasPartialMatch(), а match.hasMatch().
QRegularExpression: Поиск всех совпадений
QRegularExpression умеет искать и все совпадения. Делает он это с помощью итераторов в стиле Java:
1 2 3 4 5 6 7 8 9 |
void globalMatchingTest() { QRegularExpression re( "#include <([^>]+)>" ); auto it = re.globalMatch( TEXT ); while( it.hasNext() ) { auto match = it.next(); qDebug() << match.captured( 0 ) << ":" << match.captured( 1 ); } } |
Следует заметить, что при выводе результатов на консоль мы использовали функцию-член match.captured(). Для нуля она возвращает всю подстроку, соответствующую шаблону, а для единицы — первую группу регулярного выражения, завернутую в скобки.
Поиск будем проводить по следующему фрагменту:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static const char* const TEXT = "#include <QDebug>\n" "#include <QRegularExpression>\n" "\n" "int main() {\n" "QRegularExpression re( \"#include <([^>]+)>\" );\n" "auto it = re.globalMatch( TEXT );\n" "while( it.hasNext() ) {\n" "auto match = it.next();\n" "qDebug() << match.captured( 0 ) << \":\" << match.captured( 1 );\n" "}\n" "return 0;\n" "}"; |
В итоге имеем:
1 2 3 |
"#include <QDebug>" : "QDebug" "#include <QRegularExpression>" : "QRegularExpression" "#include <([^>" : "([^" |
QRegularExpression: Замена текста
Замена текста по регулярному выражению — довольно важная и часто встречающаяся задача. С помощью QRegularExpression она решается так же, как и для QRegExp:
1 2 3 4 5 |
void replacingTest() { QRegularExpression re( "#include <([^>]+)>" ); qDebug() << qPrintable( QString( TEXT ).replace( re, "#include \"\\1\"" ) ); } |
Приведенный пример заменяет скобки после #include с <> на "". Это достигается за счет использования групп в регулярном выражении в шаблоне замены:
1 |
"#include \"\\1\"" |
На консоль будет выведено следующее:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include "QDebug" #include "QRegularExpression" int main() { QRegularExpression re( "#include "([^"]+)>" ); auto it = re.globalMatch( TEXT ); while( it.hasNext() ) { auto match = it.next(); qDebug() << match.captured( 0 ) << ":" << match.captured( 1 ); } return 0; } |