qa: Remove lots of superfluous semicolons
[quassel.git] / src / common / expressionmatch.h
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #pragma once
22
23 #include <QRegularExpression>
24 #include <QString>
25 #include <QStringList>
26
27 /**
28  * Expression matcher with multiple modes of operation and automatic caching for performance
29  */
30 class ExpressionMatch
31 {
32
33 public:
34     /// Expression matching mode
35     enum class MatchMode {
36         MatchPhrase,        ///< Match phrase as specified, no special handling
37         MatchMultiPhrase,   ///< Match phrase as specified, split on \n only
38         MatchWildcard,      ///< Match wildcards, "!" at start inverts, "\" escapes
39         MatchMultiWildcard, ///< Match wildcards, split ; or \n, "!" at start inverts, "\" escapes
40         MatchRegEx          ///< Match as regular expression, "!..." invert regex, "\" escapes
41     };
42
43     /**
44      * Construct an empty ExpressionMatch
45      */
46     ExpressionMatch() {}
47
48     /**
49      * Construct an Expression match with the given parameters
50      *
51      * @param expression     A phrase, wildcard expression, or regular expression
52      * @param mode
53      * @parblock
54      * Expression matching mode
55      * @see ExpressionMatch::MatchMode
56      * @endparblock
57      * @param caseSensitive  If true, match case-sensitively, otherwise ignore case when matching
58      */
59     ExpressionMatch(const QString &expression, MatchMode mode, bool caseSensitive);
60
61     /**
62      * Check if the given string matches the stored expression
63      *
64      * @param string      String to check
65      * @param matchEmpty  If true, always match when the expression is empty, otherwise never match
66      * @return            True if match found, otherwise false
67      */
68     bool match(const QString &string, bool matchEmpty = false) const;
69
70     /**
71      * Gets if the source expression is empty
72      *
73      * @return True if source expression is empty, otherwise false
74      */
75     inline bool isEmpty() const { return (_sourceExpressionEmpty); }
76
77     /**
78      * Gets if the source expression and parameters resulted in a valid expression matcher
79      *
80      * @return True if given expression is valid, otherwise false
81      */
82     inline bool isValid() const {
83         // Either this must be empty, or normal or inverted rules must be valid and active
84         return (_sourceExpressionEmpty
85                 || (_matchRegExActive && _matchRegEx.isValid())
86                 || (_matchInvertRegExActive && _matchInvertRegEx.isValid()));
87     }
88
89     /**
90      * Gets the original expression match string
91      *
92      * @return QString of the source expression match string
93      */
94     inline QString sourceExpression() const { return _sourceExpression; }
95
96     /**
97      * Sets the expression match string
98      *
99      * @param expression A phrase, wildcard expression, or regular expression
100      */
101     void setSourceExpression(const QString &expression) {
102         if (_sourceExpression != expression) {
103             _sourceExpression = expression;
104             cacheRegEx();
105         }
106     }
107
108     /**
109      * Gets the original expression match mode
110      *
111      * @return MatchMode of the source expression
112      */
113     inline MatchMode sourceMode() const { return _sourceMode; }
114
115     /**
116      * Sets the expression match mode
117      *
118      * @param mode
119      * @parblock
120      * Expression matching mode
121      * @see ExpressionMatch::MatchMode
122      * @endparblock
123      */
124     void setSourceMode(MatchMode mode) {
125         if (_sourceMode != mode) {
126             _sourceMode = mode;
127             cacheRegEx();
128         }
129     }
130
131     /**
132      * Gets the original expression case-sensitivity
133      *
134      * @return True if case-sensitive, otherwise false
135      */
136     inline bool sourceCaseSensitive() const { return _sourceCaseSensitive; }
137
138     /**
139      * Sets the expression match as case sensitive or not
140      *
141      * @param caseSensitive If true, match case-sensitively, otherwise ignore case when matching
142      */
143     void setSourceCaseSensitive(bool caseSensitive) {
144         if (_sourceCaseSensitive != caseSensitive) {
145             _sourceCaseSensitive = caseSensitive;
146             cacheRegEx();
147         }
148     }
149
150     bool operator!=(const ExpressionMatch &other) const
151     {
152         return (_sourceExpression != other._sourceExpression ||
153                 _sourceMode != other._sourceMode ||
154                 _sourceCaseSensitive != other._sourceCaseSensitive);
155     }
156
157     /**
158      * Trim extraneous whitespace from individual rules within a given MultiWildcard expression
159      *
160      * This respects the ";" escaping rules with "\".  It is safe to call this multiple times; a
161      * trimmed string should remain unchanged.
162      *
163      * @see ExpressionMatch::MatchMode::MatchMultiWildcard
164      *
165      * @param originalRule MultiWildcard rule list, ";"-separated
166      * @return Trimmed MultiWildcard rule list
167      */
168     static QString trimMultiWildcardWhitespace(const QString &originalRule);
169
170 private:
171     /**
172      * Calculates internal regular expressions
173      *
174      * Will always run when called, no cache validity checks performed.
175      */
176     void cacheRegEx();
177
178     /**
179      * Creates a regular expression object of appropriate type and case-sensitivity
180      *
181      * @param regExString    Regular expression string
182      * @param caseSensitive  If true, match case-sensitively, otherwise ignore case when matching
183      * @return Configured QRegularExpression
184      */
185     static QRegularExpression regExFactory(const QString &regExString, bool caseSensitive);
186
187     /**
188      * Escapes any regular expression characters in a string so they have no special meaning
189      *
190      * @param phrase String containing potential regular expression special characters
191      * @return QString with all regular expression characters escaped
192      */
193     static QString regExEscape(const QString &phrase);
194
195     /**
196      * Converts a multiple-phrase rule into a regular expression
197      *
198      * Unconditionally splits phrases on "\n", whitespace is preserved
199      *
200      * @param originalRule MultiPhrase rule list, "\n"-separated
201      * @return A regular expression matching the given phrases
202      */
203     static QString convertFromMultiPhrase(const QString &originalRule);
204
205     /**
206      * Internally converts a wildcard rule into regular expressions
207      *
208      * Splits wildcards on ";" and "\n", "!..." inverts section, "\" escapes
209      *
210      * @param originalRule   MultiWildcard rule list, ";"-separated
211      * @param caseSensitive  If true, match case-sensitively, otherwise ignore case when matching
212      */
213     void generateFromMultiWildcard(const QString &originalRule, bool caseSensitive);
214
215     /**
216      * Converts a wildcard expression into a regular expression
217      *
218      * NOTE:  Does not handle Quassel's extended scope matching and splitting.
219      *
220      * @see ExpressionMatch::convertFromWildcard()
221      * @return QString with all regular expression characters escaped
222      */
223     static QString wildcardToRegEx(const QString &expression);
224
225     // Original/source components
226     QString _sourceExpression = {};                  ///< Expression match string given on creation
227     MatchMode _sourceMode = MatchMode::MatchPhrase;  ///< Expression match mode given on creation
228     bool _sourceCaseSensitive = false;               ///< Expression case sensitive on creation
229
230     // Derived components
231     bool _sourceExpressionEmpty = false;             ///< Cached expression match string is empty
232
233     /// Underlying regular expression matching instance for normal (noninverted) rules
234     QRegularExpression _matchRegEx = {};
235     bool _matchRegExActive = false;                  ///< If true, use normal expression in matching
236
237     /// Underlying regular expression matching instance for inverted rules
238     QRegularExpression _matchInvertRegEx = {};
239     bool _matchInvertRegExActive = false;            ///< If true, use invert expression in matching
240 };