1
/* This file is part of the Baloo query parser
2
Copyright (c) 2013 Denis Steckelmacher <steckdenis@yahoo.fr>
4
This library is free software; you can redistribute it and/or
5
modify it under the terms of the GNU Library General Public
6
License version 2.1 as published by the Free Software Foundation,
9
This library is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
Library General Public License for more details.
14
You should have received a copy of the GNU Library General Public License
15
along with this library; see the file COPYING.LIB. If not, write to
16
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
Boston, MA 02110-1301, USA.
20
#include "patternmatcher.h"
21
#include "queryparser.h"
26
PatternMatcher::PatternMatcher(Baloo::QueryParser *parser,
27
QList<Baloo::Term> &terms,
29
const QStringList &pattern,
30
Baloo::CompletionProposal::Type completion_type,
31
const KLocalizedString &completion_description)
34
cursor_position(cursor_position),
36
completion_type(completion_type),
37
completion_description(completion_description),
38
capture_count(captureCount())
42
int PatternMatcher::captureCount() const
47
Q_FOREACH(const QString &p, pattern) {
48
if (p.at(0) == QLatin1Char('%')) {
49
capture = p.mid(1).toInt();
51
if (capture > max_capture) {
52
max_capture = capture;
60
int PatternMatcher::matchPattern(int first_term_index,
61
QList<Baloo::Term> &matched_terms,
63
int &end_position) const
65
int pattern_index = 0;
66
int term_index = first_term_index;
67
bool has_matched_a_literal = false;
68
bool match_anything = false; // Match "%%"
69
bool contains_catchall = false;
71
start_position = 1 << 30;
74
while (pattern_index < pattern.count() && term_index < terms.count()) {
75
const Baloo::Term &term = terms.at(term_index);
76
int capture_index = -1;
78
// Always update start and end position, they will be simply discarded
79
// if the pattern ends not matching.
80
start_position = qMin(start_position, termStart(term));
81
end_position = qMax(end_position, termEnd(term));
83
if (pattern.at(pattern_index) == QLatin1String("%%")) {
84
// Start to match anything
85
match_anything = true;
86
contains_catchall = true;
92
bool match = matchTerm(term, pattern.at(pattern_index), capture_index);
96
// The stop pattern is not yet matched, continue to match
98
matched_terms.append(term);
100
// The terminating pattern is matched, stop to match anything
101
match_anything = false;
105
if (capture_index != -1) {
106
matched_terms[capture_index] = term;
108
has_matched_a_literal = true; // At least one literal has been matched, enable auto-completion
111
// Try to match the next pattern
114
// The pattern does not match, abort
118
// Match the next term
122
// See if the partially matched pattern can be used to provide a completion proposal
123
if (has_matched_a_literal && term_index - first_term_index > 0) {
124
addCompletionProposal(pattern_index, first_term_index, term_index);
127
if (contains_catchall || pattern_index == pattern.count()) {
128
// Patterns containing "%%" typically end with an optional terminating
129
// term. Allow them to match even if we reach the end of the term list
130
// without encountering the terminating term.
131
return (term_index - first_term_index);
137
bool PatternMatcher::matchTerm(const Baloo::Term &term, const QString &pattern, int &capture_index) const
139
if (pattern.at(0) == QLatin1Char('%')) {
141
capture_index = pattern.mid(1).toInt() - 1;
145
// Literal value that has to be matched against a regular expression
146
QString value = stringValueIfLiteral(term);
147
QStringList allowed_values = pattern.split(QLatin1Char('|'));
149
if (value.isNull()) {
153
Q_FOREACH(const QString &allowed_value, allowed_values) {
154
if (QRegExp(allowed_value, Qt::CaseInsensitive, QRegExp::RegExp2).exactMatch(value)) {
163
void PatternMatcher::addCompletionProposal(int first_pattern_index_not_matching,
164
int first_term_index_matching,
165
int first_term_index_not_matching) const
167
// Don't count terms that are not literal terms. This avoids problems when the
168
// user types "sent to size > 2M", that is seen here as "sent to <comparison>".
169
if (!terms.at(first_term_index_not_matching - 1).property().isNull()) {
170
if (--first_term_index_not_matching < 0) {
171
return; // Avoid an underflow when the pattern is only "%1" for instance.
175
const Baloo::Term &first_matching = terms.at(first_term_index_matching);
176
const Baloo::Term &last_matching = terms.at(first_term_index_not_matching - 1);
178
// Check that the completion proposal would be valid
179
if (completion_description.isEmpty()) {
183
if (cursor_position < termStart(first_matching)) {
187
if (first_term_index_not_matching < terms.count() &&
188
cursor_position > termStart(terms.at(first_term_index_not_matching))) {
192
// Replace pattern parts that have matched with their matched terms, and
193
// replace "a|b|c" with "a". This makes the pattern nicer for the user
194
int pattern_parts_to_replace = first_term_index_not_matching - first_term_index_matching;
195
QStringList user_friendly_pattern(pattern);
197
for (int i=0; i<user_friendly_pattern.count(); ++i) {
198
QString &part = user_friendly_pattern[i];
200
if (part == QLatin1String("%%")) {
201
// pattern_parts_to_replace and i cannot be used as %% can hide
202
// many terms matched by the pattern. Don't touch the pattern.
204
} else if (part.startsWith(QLatin1Char('%'))) {
208
if (i < pattern_parts_to_replace) {
209
part = terms.at(first_term_index_matching + i).value().toString();
211
part = part.section(QLatin1Char('|'), 0, 0);
215
parser->addCompletionProposal(new Baloo::CompletionProposal(
216
user_friendly_pattern,
217
first_pattern_index_not_matching - 1,
218
termStart(first_matching),
219
termEnd(last_matching) - termStart(first_matching) + 1,
221
completion_description