Refactor wildcard support in QRegularExpression

The API originally proposed was flawed in the sense that the setter
function would use a modified version of the parameter given which would
have make it a black box for the user. This patch fixes that by removing
that setter and providing a static method that will return the pattern
suitably modified to be used by QRegularExpression the same way the
escape method does.

[ChangeLog][Core][QRegularExpression] Implemented support for wildcard
patterns through a static method.

Change-Id: I0054bcaffd7525dac569f54fa81f73b7e4544b2e
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Samuel Gaist 2018-08-01 00:07:11 +02:00
parent 0f6b941ca5
commit 30b0701c9b
6 changed files with 133 additions and 201 deletions

View File

@ -350,4 +350,11 @@ while (i.hasNext()) {
//! [30]
}
{
//! [31]
QString wildcard = QRegularExpression::wildcardToRegularExpression("*.jpeg");
// wilcard == ".*\.jpeg"
//! [31]
}
}

View File

@ -516,10 +516,10 @@ QT_BEGIN_NAMESPACE
\section2 Wildcard matching
There is no equivalent of wildcard matching in QRegularExpression.
Nevertheless, rewriting a regular expression in wildcard syntax to a
Perl-compatible regular expression is a very easy task, given the fact
that wildcard syntax supported by QRegExp is very simple.
There is no direct way to do wildcard matching in QRegularExpression.
However, the wildcardToRegularExpression method is provided to translate
glob patterns into a Perl-compatible regular expression that can be used
for that purpose.
\section2 Other pattern syntaxes
@ -784,82 +784,6 @@ QT_BEGIN_NAMESPACE
Qt 5.4.
*/
namespace QtPrivate {
/*!
internal
*/
QString wildcardToRegularExpression(const QString &wildcardString)
{
const int wclen = wildcardString.length();
QString rx;
int i = 0;
bool hasNegativeBracket = false;
const QChar *wc = wildcardString.unicode();
while (i < wclen) {
const QChar c = wc[i++];
switch (c.unicode()) {
case '*':
rx += QLatin1String(".*");
break;
case '?':
rx += QLatin1Char('.');
break;
case '$':
case '(':
case ')':
case '+':
case '.':
case '^':
case '{':
case '|':
case '}':
rx += QLatin1Char('\\');
rx += c;
break;
case '[':
// Support for the [!abc] or [!a-c] syntax
// Implements a negative look-behind for one char.
if (wc[i] == QLatin1Char(']')) {
rx += c;
rx += wc[i++];
} else if (wc[i] == QLatin1Char('!')) {
rx += QLatin1String(".(?<");
rx += wc[i++];
rx += c;
hasNegativeBracket = true;
} else {
rx += c;
}
if (i < wclen) {
if (rx[i] == QLatin1Char(']'))
rx += wc[i++];
while (i < wclen && wc[i] != QLatin1Char(']')) {
if (wc[i] == QLatin1Char('\\'))
rx += QLatin1Char('\\');
rx += wc[i++];
}
}
break;
case ']':
rx += c;
// Closes the negative look-behind expression.
if (hasNegativeBracket) {
rx += QLatin1Char(')');
hasNegativeBracket = false;
}
break;
default:
rx += c;
break;
}
}
return rx;
}
}
/*!
\internal
*/
@ -1582,47 +1506,6 @@ void QRegularExpression::setPattern(const QString &pattern)
d->pattern = pattern;
}
/*!
\since 5.12
Sets the pattern string of the regular expression to \a wildcard pattern.
The pattern options are left unchanged.
\warning Unlike QRegExp, this implementation follows closely the definition
of wildcard for glob patterns:
\table
\row \li \b{c}
\li Any character represents itself apart from those mentioned
below. Thus \b{c} matches the character \e c.
\row \li \b{?}
\li Matches any single character. It is the same as
\b{.} in full regexps.
\row \li \b{*}
\li Matches zero or more of any characters. It is the
same as \b{.*} in full regexps.
\row \li \b{[abc]}
\li Matches one character given in the bracket.
\row \li \b{[a-c]}
\li Matches one character from the range given in the bracket.
\row \li \b{[!abc]}
\li Matches one character that is not given in the bracket.
\row \li \b{[!a-c]}
\li matches one character that is not from the range given in the
bracket.
\endtable
\note This function generates a regular expression that will act following
the wildcard pattern given. However the content of the regular expression
will not be the same as the one set.
\sa pattern(), setPattern()
*/
void QRegularExpression::setWildcardPattern(const QString &pattern)
{
setPattern(QtPrivate::wildcardToRegularExpression(pattern));
}
/*!
Returns the pattern options for the regular expression.
@ -1987,6 +1870,119 @@ QString QRegularExpression::escape(const QString &str)
return result;
}
/*!
\since 5.12
Returns a regular expression representation of the given glob \a pattern.
\snippet code/src_corelib_tools_qregularexpression.cpp 31
\warning Unlike QRegExp, this implementation follows closely the definition
of wildcard for glob patterns:
\table
\row \li \b{c}
\li Any character represents itself apart from those mentioned
below. Thus \b{c} matches the character \e c.
\row \li \b{?}
\li Matches any single character. It is the same as
\b{.} in full regexps.
\row \li \b{*}
\li Matches zero or more of any characters. It is the
same as \b{.*} in full regexps.
\row \li \b{[abc]}
\li Matches one character given in the bracket.
\row \li \b{[a-c]}
\li Matches one character from the range given in the bracket.
\row \li \b{[!abc]}
\li Matches one character that is not given in the bracket. It is the
same as \b{[^abc]} in full regexp.
\row \li \b{[!a-c]}
\li Matches one character that is not from the range given in the
bracket. It is the same as \b{[^a-c]} in full regexp.
\endtable
\note The backslash (\\) character is \e not an escape char in this context.
In order to match one of the special characters, place it in square brackets
(for example, "[?]").
More information about the implementation can be found in:
\list
\li \l {https://en.wikipedia.org/wiki/Glob_(programming)} {The Wikipedia Glob article}
\li \c man 7 glob
\endlist
\sa escape()
*/
QString QRegularExpression::wildcardToRegularExpression(const QString &pattern)
{
const int wclen = pattern.length();
QString rx;
int i = 0;
bool hasNegativeBracket = false;
const QChar *wc = pattern.unicode();
while (i < wclen) {
const QChar c = wc[i++];
switch (c.unicode()) {
case '*':
rx += QLatin1String(".*");
break;
case '?':
rx += QLatin1Char('.');
break;
case '$':
case '(':
case ')':
case '+':
case '.':
case '^':
case '{':
case '|':
case '}':
rx += QLatin1Char('\\');
rx += c;
break;
case '[':
// Support for the [!abc] or [!a-c] syntax
// Implements a negative look-behind for one char.
if (i < wclen) {
if (wc[i] == QLatin1Char(']')) {
rx += c;
rx += wc[i++];
} else if (wc[i] == QLatin1Char('!')) {
rx += QLatin1String(".(?<");
rx += wc[i++];
rx += c;
hasNegativeBracket = true;
} else {
rx += c;
}
while (i < wclen && wc[i] != QLatin1Char(']')) {
if (wc[i] == QLatin1Char('\\'))
rx += QLatin1Char('\\');
rx += wc[i++];
}
} else {
rx += c;
}
break;
case ']':
rx += c;
// Closes the negative look-behind expression.
if (hasNegativeBracket) {
rx += QLatin1Char(')');
hasNegativeBracket = false;
}
break;
default:
rx += c;
break;
}
}
return rx;
}
/*!
\since 5.1

View File

@ -96,7 +96,6 @@ public:
QString pattern() const;
void setPattern(const QString &pattern);
void setWildcardPattern(const QString &pattern);
bool isValid() const;
int patternErrorOffset() const;
@ -142,6 +141,7 @@ public:
void optimize() const;
static QString escape(const QString &str);
static QString wildcardToRegularExpression(const QString &str);
bool operator==(const QRegularExpression &re) const;
inline bool operator!=(const QRegularExpression &re) const { return !operator==(re); }

View File

@ -1,70 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2018 Samuel Gaist <samuel.gaist@edeltech.ch>
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QREGULAREXPRESSION_P_H
#define QREGULAREXPRESSION_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <private/qglobal_p.h>
#include <qregularexpression.h>
#include <qstring.h>
QT_REQUIRE_CONFIG(regularexpression);
QT_BEGIN_NAMESPACE
namespace QtPrivate {
QString wildcardToRegularExpression(const QString &expression);
}
QT_END_NAMESPACE
#endif

View File

@ -183,8 +183,7 @@ qtConfig(regularexpression) {
QMAKE_USE_PRIVATE += pcre2
HEADERS += \
tools/qregularexpression.h \
tools/qregularexpression_p.h
tools/qregularexpression.h
SOURCES += tools/qregularexpression.cpp
}

View File

@ -2171,6 +2171,10 @@ void tst_QRegularExpression::wildcard_data()
addRow(".???l", "test.html", 4);
addRow("?", "test.html", 0);
addRow("?m", "test.html", 6);
addRow("[*]", "test.html", -1);
addRow("[?]","test.html", -1);
addRow("[[]","test.h[ml", 6);
addRow("[]]","test.h]ml", 6);
addRow(".h[a-z]ml", "test.html", 4);
addRow(".h[A-Z]ml", "test.html", -1);
addRow(".h[A-Z]ml", "test.hTml", 4);
@ -2191,9 +2195,7 @@ void tst_QRegularExpression::wildcard()
QFETCH(QString, string);
QFETCH(int, foundIndex);
QRegularExpression re;
re.setWildcardPattern(pattern);
QRegularExpression re(QRegularExpression::wildcardToRegularExpression(pattern));
QRegularExpressionMatch match = re.match(string);
QCOMPARE(match.capturedStart(), foundIndex);
@ -2217,11 +2219,9 @@ void tst_QRegularExpression::testInvalidWildcard_data()
void tst_QRegularExpression::testInvalidWildcard()
{
QFETCH(QString, pattern);
QRegularExpression re;
re.setWildcardPattern(pattern);
QFETCH(bool, isValid);
QRegularExpression re(QRegularExpression::wildcardToRegularExpression(pattern));
QCOMPARE(re.isValid(), isValid);
}