common: Port remote nicks to NickHighlightMatcher
[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 <QString>
24 #include <QStringList>
25
26 #if QT_VERSION >= 0x050000
27 #include <QRegularExpression>
28 #else
29 #include <QRegExp>
30 #endif
31
32 /**
33  * Expression matcher with multiple modes of operation and automatic caching for performance
34  */
35 class ExpressionMatch
36 {
37
38 public:
39     /// Expression matching mode
40 #if QT_VERSION >= 0x050000
41     enum class MatchMode {
42 #else
43     enum MatchMode {
44 #endif
45         MatchPhrase,        ///< Match phrase as specified, no special handling
46         MatchMultiPhrase,   ///< Match phrase as specified, split on \n only
47         MatchWildcard,      ///< Match wildcards, "!" at start inverts, "\" escapes
48         MatchMultiWildcard, ///< Match wildcards, split ; or \n, "!" at start inverts, "\" escapes
49         MatchRegEx          ///< Match as regular expression, "!..." invert regex, "\" escapes
50     };
51
52     /**
53      * Construct an empty ExpressionMatch
54      */
55     ExpressionMatch() {}
56
57     /**
58      * Construct an Expression match with the given parameters
59      *
60      * @param expression     A phrase, wildcard expression, or regular expression
61      * @param mode
62      * @parblock
63      * Expression matching mode
64      * @see ExpressionMatch::MatchMode
65      * @endparblock
66      * @param caseSensitive  If true, match case-sensitively, otherwise ignore case when matching
67      */
68     ExpressionMatch(const QString &expression, MatchMode mode, bool caseSensitive);
69
70     /**
71      * Check if the given string matches the stored expression
72      *
73      * @param string      String to check
74      * @param matchEmpty  If true, always match when the expression is empty, otherwise never match
75      * @return            True if match found, otherwise false
76      */
77     bool match(const QString &string, bool matchEmpty = false) const;
78
79     /**
80      * Gets if the source expression is empty
81      *
82      * @return True if source expression is empty, otherwise false
83      */
84     inline bool isEmpty() const { return (_sourceExpressionEmpty); }
85
86     /**
87      * Gets if the source expression and parameters resulted in a valid expression matcher
88      *
89      * @return True if given expression is valid, otherwise false
90      */
91     inline bool isValid() const {
92         // Either this must be empty, or normal or inverted rules must be valid and active
93         return (_sourceExpressionEmpty
94                 || (_matchRegExActive && _matchRegEx.isValid())
95                 || (_matchInvertRegExActive && _matchInvertRegEx.isValid()));
96     }
97
98     /**
99      * Gets the original expression match string
100      *
101      * @return QString of the source expression match string
102      */
103     inline QString sourceExpression() const { return _sourceExpression; }
104
105     /**
106      * Sets the expression match string
107      *
108      * @param expression A phrase, wildcard expression, or regular expression
109      */
110     void setSourceExpression(const QString &expression) {
111         if (_sourceExpression != expression) {
112             _sourceExpression = expression;
113             cacheRegEx();
114         }
115     }
116
117     /**
118      * Gets the original expression match mode
119      *
120      * @return MatchMode of the source expression
121      */
122     inline MatchMode sourceMode() const { return _sourceMode; }
123
124     /**
125      * Sets the expression match mode
126      *
127      * @param mode
128      * @parblock
129      * Expression matching mode
130      * @see ExpressionMatch::MatchMode
131      * @endparblock
132      */
133     void setSourceMode(MatchMode mode) {
134         if (_sourceMode != mode) {
135             _sourceMode = mode;
136             cacheRegEx();
137         }
138     }
139
140     /**
141      * Gets the original expression case-sensitivity
142      *
143      * @return True if case-sensitive, otherwise false
144      */
145     inline bool sourceCaseSensitive() const { return _sourceCaseSensitive; }
146
147     /**
148      * Sets the expression match as case sensitive or not
149      *
150      * @param caseSensitive If true, match case-sensitively, otherwise ignore case when matching
151      */
152     void setSourceCaseSensitive(bool caseSensitive) {
153         if (_sourceCaseSensitive != caseSensitive) {
154             _sourceCaseSensitive = caseSensitive;
155             cacheRegEx();
156         }
157     }
158
159     bool operator!=(const ExpressionMatch &other) const
160     {
161         return (_sourceExpression != other._sourceExpression ||
162                 _sourceMode != other._sourceMode ||
163                 _sourceCaseSensitive != other._sourceCaseSensitive);
164     }
165
166     /**
167      * Trim extraneous whitespace from individual rules within a given MultiWildcard expression
168      *
169      * This respects the ";" escaping rules with "\".  It is safe to call this multiple times; a
170      * trimmed string should remain unchanged.
171      *
172      * @see ExpressionMatch::MatchMode::MatchMultiWildcard
173      *
174      * @param originalRule MultiWildcard rule list, ";"-separated
175      * @return Trimmed MultiWildcard rule list
176      */
177     static QString trimMultiWildcardWhitespace(const QString &originalRule);
178
179 private:
180     /**
181      * Calculates internal regular expressions
182      *
183      * Will always run when called, no cache validity checks performed.
184      */
185     void cacheRegEx();
186
187     /**
188      * Creates a regular expression object of appropriate type and case-sensitivity
189      *
190      * @param regExString    Regular expression string
191      * @param caseSensitive  If true, match case-sensitively, otherwise ignore case when matching
192      * @return Configured QRegExp class on Qt 4, QRegularExpression on Qt 5
193      */
194 #if QT_VERSION >= 0x050000
195     static QRegularExpression regExFactory(const QString &regExString, bool caseSensitive);
196 #else
197     static QRegExp regExFactory(const QString &regExString, bool caseSensitive);
198 #endif
199
200     /**
201      * Escapes any regular expression characters in a string so they have no special meaning
202      *
203      * @param phrase String containing potential regular expression special characters
204      * @return QString with all regular expression characters escaped
205      */
206     static QString regExEscape(const QString &phrase);
207
208     /**
209      * Converts a multiple-phrase rule into a regular expression
210      *
211      * Unconditionally splits phrases on "\n", whitespace is preserved
212      *
213      * @param originalRule MultiPhrase rule list, "\n"-separated
214      * @return A regular expression matching the given phrases
215      */
216     static QString convertFromMultiPhrase(const QString &originalRule);
217
218     /**
219      * Internally converts a wildcard rule into regular expressions
220      *
221      * Splits wildcards on ";" and "\n", "!..." inverts section, "\" escapes
222      *
223      * @param originalRule   MultiWildcard rule list, ";"-separated
224      * @param caseSensitive  If true, match case-sensitively, otherwise ignore case when matching
225      */
226     void generateFromMultiWildcard(const QString &originalRule, bool caseSensitive);
227
228     /**
229      * Converts a wildcard expression into a regular expression
230      *
231      * NOTE:  Does not handle Quassel's extended scope matching and splitting.
232      *
233      * @see ExpressionMatch::convertFromWildcard()
234      * @return QString with all regular expression characters escaped
235      */
236     static QString wildcardToRegEx(const QString &expression);
237
238     // Original/source components
239     QString _sourceExpression = {};                  ///< Expression match string given on creation
240     MatchMode _sourceMode = MatchMode::MatchPhrase;  ///< Expression match mode given on creation
241     bool _sourceCaseSensitive = false;               ///< Expression case sensitive on creation
242
243     // Derived components
244     bool _sourceExpressionEmpty = false;             ///< Cached expression match string is empty
245
246     /// Underlying regular expression matching instance for normal (noninverted) rules
247 #if QT_VERSION >= 0x050000
248     QRegularExpression _matchRegEx = {};
249 #else
250     QRegExp _matchRegEx = {};
251 #endif
252     bool _matchRegExActive = false;                  ///< If true, use normal expression in matching
253
254     /// Underlying regular expression matching instance for inverted rules
255 #if QT_VERSION >= 0x050000
256     QRegularExpression _matchInvertRegEx = {};
257 #else
258     QRegExp _matchInvertRegEx = {};
259 #endif
260     bool _matchInvertRegExActive = false;            ///< If true, use invert expression in matching
261 };