Article December 08, 2019

Qt中的正则表达式

Words count 25k Reading time 23 mins. Read count 0

QRegularExpression

QRegularExpression主要由pattern string和pattern options集合组成,构造一个对象,可以使用

1
QRegularExpression re("a pattern");

1
2
QRegularExpression re;
re.setPattern("another pattern");

如果想在pattern string中包含’',则需要使用转义”\“,如

1
2
3
4
5
// matches two digits followed by a space and a word
QRegularExpression re("\\d\\d \\w+");

// matches a backslash
QRegularExpression re2("\\\\");

Pattern Options

在构造函数中设置

1
2
3
// 设置匹配大小写
// matches "Qt rocks", but also "QT rocks", "QT ROCKS", "qT rOcKs", etc.
QRegularExpression re("Qt rocks", QRegularExpression::CaseInsensitiveOption);

通过setPatternOptions设置

1
2
3
4
QRegularExpression re("^\\d+$");
//
re.setPatternOptions(QRegularExpression::MultilineOption);
// re matches any line in the subject string that contains only digits (but at least one)

通过patternOptions获取当前设置

1
2
3
4
QRegularExpression re = QRegularExpression("^two.*words$", QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption);

QRegularExpression::PatternOptions options = re.patternOptions();
// options == QRegularExpression::MultilineOption |QRegularExpression::DotMatchesEverythingOption

Match Type and Match Options

match和globalMatch函数的最后两个参数是match type和match options,match type指定了匹配算法,默认是NormalMatch。

Normal Matching

要执行一次match,只需要调用match函数,并传递要匹配的字符串即可,返回值是一个QRegularExpressionMatch对象。

1
2
3
4
// match two digits followed by a space and a word
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("abc123 def");
bool hasMatch = match.hasMatch(); // true

如果match成功,通过QRegularExpressionMatch对象可以获取到匹配元素:

1
2
3
4
5
6
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("abc123 def");
if (match.hasMatch()) {
QString matched = match.captured(0); // matched == "23 def"
// ...
}

可以传递一个偏移值,相对于搜索串的偏移,如下,”12 abc”不会被搜索到,因为是从搜索串的偏移1开始搜索:

1
2
3
4
5
6
QRegularExpression re("\\d\\d \\w+");
QRegularExpressionMatch match = re.match("12 abc 45 def", 1);
if (match.hasMatch()) {
QString matched = match.captured(0); // matched == "45 def"
// ...
}

Extracting captured substrings

captured(int nth = 0)函数能够返回匹配组的第nth个对象:

1
2
3
4
5
6
7
8
QRegularExpression re("^(\\d\\d)/(\\d\\d)/(\\d\\d\\d\\d)$");
QRegularExpressionMatch match = re.match("08/12/1985");
if (match.hasMatch()) {
QString day = match.captured(1); // day == "08"
QString month = match.captured(2); // month == "12"
QString year = match.captured(3); // year == "1985"
// ...
}

匹配组的索引是从开始的,索引为0表示完全匹配pattern的字符串。
capturedStart(int nth = 0)和capturedEnd(int nth = 0)可以返回匹配字符串在搜索串中的位置,匹配串的长度即为 和capturedEnd - capturedStart:

1
2
3
4
5
6
7
QRegularExpression re("abc(\\d+)def");
QRegularExpressionMatch match = re.match("XYZabc123defXYZ");
if (match.hasMatch()) {
int startOffset = match.capturedStart(1); // startOffset == 6
int endOffset = match.capturedEnd(1); // endOffset == 9
// ...
}

可以给每个匹配项命名:

1
2
3
4
5
6
7
QRegularExpression re("^(?<date>\\d\\d)/(?<month>\\d\\d)/(?<year>\\d\\d\\d\\d)$");
QRegularExpressionMatch match = re.match("08/12/1985");
if (match.hasMatch()) {
QString date = match.captured("date"); // date == "08"
QString month = match.captured("month"); // month == "12"
QString year = match.captured("year"); // year == 1985
}

Global Matching// 搜索全部

如下例子匹配所有单词,其中,返回值i指向的是第一个匹配项前的位置:

1
2
QRegularExpression re("(\\w+)");
QRegularExpressionMatchIterator i = re.globalMatch("the quick fox");

globalMatch函数的返回值是一个QRegularExpressionMatchIterator对象,通过它能够获取匹配结果,hasNext为true表示还有不止一个结果,next返回下一个结果集:

1
2
3
4
5
6
7
QStringList words;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString word = match.captured(1);
words << word;
}
// words contains "the", "quick", "fox"

Partial Matching// 部分匹配

部分匹配是指搜索串已到了结尾,但还需要更多字符串去达到完全匹配的结果。

当部分匹配找到的时候,hasMatch返回false且hasPartialMatch返回true。同时可以通过captured(0)获取到部分匹配的字符串。

注意:当指定部分匹配的时候,也会同时进行完全匹配。如果有完全匹配,则hasMatch返回true而hasPartialMatch返回false。但不会存在两者同时为true的情况。

1
2
3
4
5
6
7
8
9
re.setPattern("(\\dabc\\d)");
match = re.match("1abc", 0, QRegularExpression::PartialPreferCompleteMatch);
qDebug()<<match.hasMatch()<<match.hasPartialMatch();// false true
words.clear();
if (match.hasPartialMatch()) {
QString word = match.captured(0);
words << word;
}
qDebug()<<words;// 1abc

Validating user input

假设我们要验证用户输入的日期是否符合某个格式,可以使用这样的匹配模式:

1
^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d\d?, \d\d\d\d$

以下使用的都是PartialPreferCompleteMatch模式。

只有部分匹配的情况:

1
2
3
4
5
6
7
QString pattern("^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \\d\\d?, \\d\\d\\d\\d$");
QRegularExpression re(pattern);

QString input("Jan 21,");
QRegularExpressionMatch match = re.match(input, 0, QRegularExpression::PartialPreferCompleteMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true

有全部匹配的情况:

1
2
3
4
QString input("Dec 8, 1985");
QRegularExpressionMatch match = re.match(input, 0, QRegularExpression::PartialPreferCompleteMatch);
bool hasMatch = match.hasMatch(); // true
bool hasPartialMatch = match.hasPartialMatch(); // false

另一种匹配模式的表现:
有全部匹配的情况:

1
2
3
4
5
QRegularExpression re("abc\\w+X|def");
QRegularExpressionMatch match = re.match("abcdef", 0, QRegularExpression::PartialPreferCompleteMatch);
bool hasMatch = match.hasMatch(); // true
bool hasPartialMatch = match.hasPartialMatch(); // false
QString captured = match.captured(0); // captured == "def"

abc\w+X部分匹配,而def全部匹配。

如果有多个部分匹配(但没有全部匹配),则第一个找到的将会返回:

1
2
3
4
5
QRegularExpression re("abc\\w+X|defY");
QRegularExpressionMatch match = re.match("abcdef", 0, QRegularExpression::PartialPreferCompleteMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true
QString captured = match.captured(0); // captured == "abcdef"

Incremental/multi-segment matching

1
2
3
4
QRegularExpression re("abc|ab");
QRegularExpressionMatch match = re.match("ab", 0, QRegularExpression::PartialPreferFirstMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true

当第一个abc匹配模式查找到的时候即返回,此时为部分匹配。

还有两个更加特殊的例子:

1
2
3
4
5
6
7
8
9
QRegularExpression re("abc(def)?");
QRegularExpressionMatch match = re.match("abc", 0, QRegularExpression::PartialPreferFirstMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true

QRegularExpression re("(abc)*");
QRegularExpressionMatch match = re.match("abc", 0, QRegularExpression::PartialPreferFirstMatch);
bool hasMatch = match.hasMatch(); // false
bool hasPartialMatch = match.hasPartialMatch(); // true

以上例子是因为?和*都是greedy的(贪心),它们会尽可能匹配一个或多个,所以当找到abc的时候,它们会接着查找,但此时已到达搜索串结尾,所以返回的是部分匹配。

Error Handling

可以通过QRegularExpression的isValid和errorString函数获取错误信息:

1
2
3
4
5
6
QRegularExpression invalidRe("(unmatched|parenthesis");
if (!invalidRe.isValid()) {
QString errorString = invalidRe.errorString(); // errorString == "missing )"
int errorOffset = invalidRe.patternErrorOffset(); // errorOffset == 22
// ...
}

如果QRegularExpression无效,则match函数返回的QRegularExpressionMatch对象也无效。

Unsupported Perl-compatible Regular Expressions Features

Qt未完全支持Perl的正则表达式语法,可能会在未来版本中添加。

Notes for QRegExp Users

与QRegExp的差异,略。

推荐新版本都使用QRegularExpression。

QRegExp

Qt中的正则表达式。

正则表达式由表达式、数量词和断言组成。

表达式,如单个字符’x’或’5’或某个字符集合,如[A-Z]。

数量词,指定了某个表达式出现的次数,如x{1,1}表示刚刚好出现一次x,x{1,5}则表示x最少出现一次,最多出现5次。

注意,正则表达式最好不要用来搜索配对括号或标签。因为配对的是,但在bold bolder中,第一个应该是跟第二个配对。

在正则表达式中,^和$分别表示搜索字符串的开始和结尾。如搜索串应只包含匹配项,不包含其他字符。

假设我们要匹配0~99,要匹配刚好一个数字的话,是[0-9]{1,1},那如果要匹配两个数字的话,则是[0-9]{1,2},如果像表示匹配串是整个搜索串的话,则可以写成^ [0-9]{1,2}$。

实现同样的功能,正则表达式可以有不同的写法。比如[0-9],也可以写为\d。数量词可以使用表达式本身表示,如x{1,1},可以写为x。要匹配0~99的话,也可以写成^ \d{1,2}$,同样可以写为^\d\d?$,因为?是数量词{1,2}的缩写,表示出现0或1次。?表示一个表达式时可选的。

如果我们想要匹配 ‘mail’ or ‘letter’ or ‘correspondence’,但不匹配包含它们的单词。我们可以使用’mail|letter|correspondence’,但这个同样会匹配到’email’, ‘mailman’, ‘mailer’, and ‘letterbox’。为了避免这种情况,我们需要指定搜索词的边界,指定单词的边界,可以使用\b标签,如\b(mail|letter|correspondence)\b。\b表示的是单词的位置,单词的边界是任何非词字符,如空格,换行,或者字符串的开头或结尾。

如果我们想要将’&’替换为html entity ‘&amp’,如果简单使用正则表达式’&’,则会匹配到原本已经转换过的&amp,这不是我们想要的。此时,可以使用负向后行断言,’(?!__)’,所以可以写成’&(?!amp;)’,就能够匹配到那些不是不是&amp中的&。

Characters and Abbreviations for Sets of Characters

Element Meaning
c 字符c
\c 字符c
\a ASCII bell (BEL, 0x07)
\f ASCII form feed (FF, 0x0C)
\n ASCII line feed (LF, 0x0A, Unix newline).
\r ASCII carriage return (CR, 0x0D).
\t ASCII horizontal tab (HT, 0x09).
\v ASCII vertical tab (VT, 0x0B).
\xhhhh Unicode character corresponding to the hexadecimal number hhhh (between 0x0000 and 0xFFFF).
\0ooo (i.e., \zero ooo) ASCII/Latin1 character for the octal number ooo (between 0 and 0377).
. (dot) 任意字符,包括换行符
\d 数字
\D 非数字
\s 空格字符
\S 非空格字符
\w word字符,包括字母、数字、下划线
\W 非word字符
\s 空格字符
\S 非空格字符

Sets of Characters

[]中括号表示一个集合,包含在里面的表达式都会进行匹配。但有两种特殊情况:

中括号内的^表示不包括,比如[abc]表示匹配a或b或c,但[^abc]就表示包含除了a和b和c以外的所有字符。

中括号的-表示一个连续的序列,比如[W-Z]表示W或X或Y或Z。

Quantifiers

E代表表达式,表达式可以是一个字符,或一个字符集的缩略表示,或者中括号表示的字符集,或者用括号包含的表达式。

表达式 备注
E? E出现0次或1次,E是可选的。跟E{0,1}是一样的,如,dents?能匹配到’dent’或’dents’.
E+ E出现一次或多次,跟E{1,}一样,如,0+能匹配到 ‘0’, ‘00’, ‘000’等等.
E* E出现0次或多次,跟E{0,}是一样的.
E{n} E出现正好n次.
E{n,} E出现最少n次.
E{,m} E出现最多m次.
E{n,m} E出现最少n次,最多m次.

注意,在使用数量词的时候,最好使用括号括起来,比如,tag+匹配包含那些ta和至少出现一个g的。而(tag)+匹配最少出现一个tag的。

Capturing Text

略。

Assertions

表达式 备注
^ 表示字符串的起始位置,如果想匹配’^’字符,则需要使用\^
$ 表示字符串的终止位置,如果想匹配’$’字符,则需要使用\$
\b word边界,注意是不包含空格的,比如将(\bOK\b)应用到’It’s OK now’中,将只会匹配’OK’
\B 非word边界,与\b的作用相反。将’\Bon\B’应用到”Left on”将会失败,但应用到”tonne”则会成功
(?=E) 正向先行断言,比如,将’const(?=\s+char)’应用到’static const char *’中,将会匹配’const’。区别于’const\s+char’,将匹配’const char’
(?!E) 反向先行断言,比如,将’const(?=\s+char)’将匹配那些后面不接’char’的’const’

Wildcard Matching(通配符匹配)

表达式 备注
c 字符c
? 匹配任意单个字符,同正则表达式中的’.’作用一样
* 匹配0个或多个字符,同正则表达式中的’.*’作用一样
[…] 匹配任意单个字符,同正则表达式中的’.’作用一样

使用exactMatch函数检测字符串是否符合通配符模式:

1
2
3
4
QRegExp rx("*.txt");
rx.setPatternSyntax(QRegExp::Wildcard);
rx.exactMatch("README.txt"); // returns true
rx.exactMatch("welcome.txt.bak"); // returns false
0%