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