src: Yearly copyright bump
[quassel.git] / src / common / expressionmatch.h
1 /***************************************************************************
2  *   Copyright (C) 2005-2019 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 public:
35     /// Expression matching mode
36     enum class MatchMode
37     {
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() = default;
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     {
86         // Either this must be empty, or normal or inverted rules must be valid and active
87         return (_sourceExpressionEmpty || (_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     {
105         if (_sourceExpression != expression) {
106             _sourceExpression = expression;
107             cacheRegEx();
108         }
109     }
110
111     /**
112      * Gets the original expression match mode
113      *
114      * @return MatchMode of the source expression
115      */
116     inline MatchMode sourceMode() const { return _sourceMode; }
117
118     /**
119      * Sets the expression match mode
120      *
121      * @param mode
122      * @parblock
123      * Expression matching mode
124      * @see ExpressionMatch::MatchMode
125      * @endparblock
126      */
127     void setSourceMode(MatchMode mode)
128     {
129         if (_sourceMode != mode) {
130             _sourceMode = mode;
131             cacheRegEx();
132         }
133     }
134
135     /**
136      * Gets the original expression case-sensitivity
137      *
138      * @return True if case-sensitive, otherwise false
139      */
140     inline bool sourceCaseSensitive() const { return _sourceCaseSensitive; }
141
142     /**
143      * Sets the expression match as case sensitive or not
144      *
145      * @param caseSensitive If true, match case-sensitively, otherwise ignore case when matching
146      */
147     void setSourceCaseSensitive(bool caseSensitive)
148     {
149         if (_sourceCaseSensitive != caseSensitive) {
150             _sourceCaseSensitive = caseSensitive;
151             cacheRegEx();
152         }
153     }
154
155     bool operator!=(const ExpressionMatch& other) const
156     {
157         return (_sourceExpression != other._sourceExpression || _sourceMode != other._sourceMode
158                 || _sourceCaseSensitive != other._sourceCaseSensitive);
159     }
160
161     /**
162      * Trim extraneous whitespace from individual rules within a given MultiWildcard expression
163      *
164      * This respects the ";" escaping rules with "\".  It is safe to call this multiple times; a
165      * trimmed string should remain unchanged.
166      *
167      * @see ExpressionMatch::MatchMode::MatchMultiWildcard
168      *
169      * @param originalRule MultiWildcard rule list, ";"-separated
170      * @return Trimmed MultiWildcard rule list
171      */
172     static QString trimMultiWildcardWhitespace(const QString& originalRule);
173
174 private:
175     /**
176      * Calculates internal regular expressions
177      *
178      * Will always run when called, no cache validity checks performed.
179      */
180     void cacheRegEx();
181
182     /**
183      * Creates a regular expression object of appropriate type and case-sensitivity
184      *
185      * @param regExString    Regular expression string
186      * @param caseSensitive  If true, match case-sensitively, otherwise ignore case when matching
187      * @return Configured QRegularExpression
188      */
189     static QRegularExpression regExFactory(const QString& regExString, bool caseSensitive);
190
191     /**
192      * Escapes any regular expression characters in a string so they have no special meaning
193      *
194      * @param phrase String containing potential regular expression special characters
195      * @return QString with all regular expression characters escaped
196      */
197     static QString regExEscape(const QString& phrase);
198
199     /**
200      * Converts a multiple-phrase rule into a regular expression
201      *
202      * Unconditionally splits phrases on "\n", whitespace is preserved
203      *
204      * @param originalRule MultiPhrase rule list, "\n"-separated
205      * @return A regular expression matching the given phrases
206      */
207     static QString convertFromMultiPhrase(const QString& originalRule);
208
209     /**
210      * Internally converts a wildcard rule into regular expressions
211      *
212      * Splits wildcards on ";" and "\n", "!..." inverts section, "\" escapes
213      *
214      * @param originalRule   MultiWildcard rule list, ";"-separated
215      * @param caseSensitive  If true, match case-sensitively, otherwise ignore case when matching
216      */
217     void generateFromMultiWildcard(const QString& originalRule, bool caseSensitive);
218
219     /**
220      * Converts a wildcard expression into a regular expression
221      *
222      * NOTE:  Does not handle Quassel's extended scope matching and splitting.
223      *
224      * @see ExpressionMatch::convertFromWildcard()
225      * @return QString with all regular expression characters escaped
226      */
227     static QString wildcardToRegEx(const QString& expression);
228
229     // Original/source components
230     QString _sourceExpression = {};                  ///< Expression match string given on creation
231     MatchMode _sourceMode = MatchMode::MatchPhrase;  ///< Expression match mode given on creation
232     bool _sourceCaseSensitive = false;               ///< Expression case sensitive on creation
233
234     // Derived components
235     bool _sourceExpressionEmpty = false;  ///< Cached expression match string is empty
236
237     /// Underlying regular expression matching instance for normal (noninverted) rules
238     QRegularExpression _matchRegEx = {};
239     bool _matchRegExActive = false;  ///< If true, use normal expression in matching
240
241     /// Underlying regular expression matching instance for inverted rules
242     QRegularExpression _matchInvertRegEx = {};
243     bool _matchInvertRegExActive = false;  ///< If true, use invert expression in matching
244 };