From b96c1e92a31ac565f406251d4f386d042519c558 Mon Sep 17 00:00:00 2001 From: Aetf Date: Fri, 1 Nov 2019 16:23:26 -0400 Subject: [PATCH] Expose EntrySearcher's SearchTerm for internal code usage --- src/core/EntrySearcher.cpp | 89 ++++++++++++++++++++++++------------- src/core/EntrySearcher.h | 31 +++++++------ tests/TestEntrySearcher.cpp | 44 +++++++++--------- 3 files changed, 97 insertions(+), 67 deletions(-) diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index 79e43e71a..21b86a7a1 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -28,6 +28,20 @@ EntrySearcher::EntrySearcher(bool caseSensitive) { } +/** + * Search group, and its children, directly by provided search terms + * @param searchTerms search terms + * @param baseGroup group to start search from, cannot be null + * @param forceSearch ignore group search settings + * @return list of entries that match the search terms + */ +QList EntrySearcher::search(const QList& searchTerms, const Group* baseGroup, bool forceSearch) +{ + Q_ASSERT(baseGroup); + m_searchTerms = searchTerms; + return repeat(baseGroup, forceSearch); +} + /** * Search group, and its children, by parsing the provided search * string for search terms. @@ -69,6 +83,19 @@ QList EntrySearcher::repeat(const Group* baseGroup, bool forceSearch) return results; } +/** + * Search provided entries by the provided search terms + * + * @param searchTerms search terms + * @param entries list of entries to include in the search + * @return list of entries that match the search terms + */ +QList EntrySearcher::searchEntries(const QList& searchTerms, const QList& entries) +{ + m_searchTerms = searchTerms; + return repeatEntries(entries); +} + /** * Search provided entries by parsing the search string * for search terms. @@ -124,46 +151,46 @@ bool EntrySearcher::searchEntryImpl(Entry* entry) bool found; for (const auto& term : m_searchTerms) { - switch (term->field) { + switch (term.field) { case Field::Title: - found = term->regex.match(entry->resolvePlaceholder(entry->title())).hasMatch(); + found = term.regex.match(entry->resolvePlaceholder(entry->title())).hasMatch(); break; case Field::Username: - found = term->regex.match(entry->resolvePlaceholder(entry->username())).hasMatch(); + found = term.regex.match(entry->resolvePlaceholder(entry->username())).hasMatch(); break; case Field::Password: - found = term->regex.match(entry->resolvePlaceholder(entry->password())).hasMatch(); + found = term.regex.match(entry->resolvePlaceholder(entry->password())).hasMatch(); break; case Field::Url: - found = term->regex.match(entry->resolvePlaceholder(entry->url())).hasMatch(); + found = term.regex.match(entry->resolvePlaceholder(entry->url())).hasMatch(); break; case Field::Notes: - found = term->regex.match(entry->notes()).hasMatch(); + found = term.regex.match(entry->notes()).hasMatch(); break; - case Field::AttributeKey: - found = !attributes.filter(term->regex).empty(); + case Field::AttributeKV: + found = !attributes.filter(term.regex).empty(); break; case Field::Attachment: - found = !attachments.filter(term->regex).empty(); + found = !attachments.filter(term.regex).empty(); break; case Field::AttributeValue: // skip protected attributes - if (entry->attributes()->isProtected(term->word)) { + if (entry->attributes()->isProtected(term.word)) { continue; } - found = entry->attributes()->contains(term->word) - && term->regex.match(entry->attributes()->value(term->word)).hasMatch(); + found = entry->attributes()->contains(term.word) + && term.regex.match(entry->attributes()->value(term.word)).hasMatch(); break; default: // Terms without a specific field try to match title, username, url, and notes - found = term->regex.match(entry->resolvePlaceholder(entry->title())).hasMatch() - || term->regex.match(entry->resolvePlaceholder(entry->username())).hasMatch() - || term->regex.match(entry->resolvePlaceholder(entry->url())).hasMatch() - || term->regex.match(entry->notes()).hasMatch(); + found = term.regex.match(entry->resolvePlaceholder(entry->title())).hasMatch() + || term.regex.match(entry->resolvePlaceholder(entry->username())).hasMatch() + || term.regex.match(entry->resolvePlaceholder(entry->url())).hasMatch() + || term.regex.match(entry->notes()).hasMatch(); } // Short circuit if we failed to match or we matched and are excluding this term - if ((!found && !term->exclude) || (found && term->exclude)) { + if ((!found && !term.exclude) || (found && term.exclude)) { return false; } } @@ -175,7 +202,7 @@ void EntrySearcher::parseSearchTerms(const QString& searchString) { static const QList> fieldnames{ {QStringLiteral("attachment"), Field::Attachment}, - {QStringLiteral("attribute"), Field::AttributeKey}, + {QStringLiteral("attribute"), Field::AttributeKV}, {QStringLiteral("notes"), Field::Notes}, {QStringLiteral("pw"), Field::Password}, {QStringLiteral("password"), Field::Password}, @@ -188,44 +215,44 @@ void EntrySearcher::parseSearchTerms(const QString& searchString) auto results = m_termParser.globalMatch(searchString); while (results.hasNext()) { auto result = results.next(); - auto term = QSharedPointer::create(); + SearchTerm term{}; // Quoted string group - term->word = result.captured(3); + term.word = result.captured(3); // If empty, use the unquoted string group - if (term->word.isEmpty()) { - term->word = result.captured(4); + if (term.word.isEmpty()) { + term.word = result.captured(4); } // If still empty, ignore this match - if (term->word.isEmpty()) { + if (term.word.isEmpty()) { continue; } auto mods = result.captured(1); // Convert term to regex - term->regex = Tools::convertToRegex(term->word, !mods.contains("*"), mods.contains("+"), m_caseSensitive); + term.regex = Tools::convertToRegex(term.word, !mods.contains("*"), mods.contains("+"), m_caseSensitive); // Exclude modifier - term->exclude = mods.contains("-") || mods.contains("!"); + term.exclude = mods.contains("-") || mods.contains("!"); // Determine the field to search - term->field = Field::Undefined; + term.field = Field::Undefined; QString field = result.captured(2); if (!field.isEmpty()) { if (field.startsWith("_", Qt::CaseInsensitive)) { - term->field = Field::AttributeValue; + term.field = Field::AttributeValue; // searching a custom attribute - // in this case term->word is the attribute key (removing the leading "_") - // and term->regex is used to match attribute value - term->word = field.mid(1); + // in this case term.word is the attribute key (removing the leading "_") + // and term.regex is used to match attribute value + term.word = field.mid(1); } else { for (const auto& pair : fieldnames) { if (pair.first.startsWith(field, Qt::CaseInsensitive)) { - term->field = pair.second; + term.field = pair.second; break; } } diff --git a/src/core/EntrySearcher.h b/src/core/EntrySearcher.h index 4a3394924..2300fcf29 100644 --- a/src/core/EntrySearcher.h +++ b/src/core/EntrySearcher.h @@ -28,18 +28,6 @@ class Entry; class EntrySearcher { public: - explicit EntrySearcher(bool caseSensitive = false); - - QList search(const QString& searchString, const Group* baseGroup, bool forceSearch = false); - QList repeat(const Group* baseGroup, bool forceSearch = false); - - QList searchEntries(const QString& searchString, const QList& entries); - QList repeatEntries(const QList& entries); - - void setCaseSensitive(bool state); - bool isCaseSensitive(); - -private: enum class Field { Undefined, @@ -48,7 +36,7 @@ private: Password, Url, Notes, - AttributeKey, + AttributeKV, Attachment, AttributeValue }; @@ -56,17 +44,32 @@ private: struct SearchTerm { Field field; + // only used when field == Field::AttributeValue QString word; QRegularExpression regex; bool exclude; }; + explicit EntrySearcher(bool caseSensitive = false); + + QList search(const QList& searchTerms, const Group* baseGroup, bool forceSearch = false); + QList search(const QString& searchString, const Group* baseGroup, bool forceSearch = false); + QList repeat(const Group* baseGroup, bool forceSearch = false); + + QList searchEntries(const QList& searchTerms, const QList& entries); + QList searchEntries(const QString& searchString, const QList& entries); + QList repeatEntries(const QList& entries); + + void setCaseSensitive(bool state); + bool isCaseSensitive(); + +private: bool searchEntryImpl(Entry* entry); void parseSearchTerms(const QString& searchString); bool m_caseSensitive; QRegularExpression m_termParser; - QList> m_searchTerms; + QList m_searchTerms; friend class TestEntrySearcher; }; diff --git a/tests/TestEntrySearcher.cpp b/tests/TestEntrySearcher.cpp index 7b129df17..7107cff0a 100644 --- a/tests/TestEntrySearcher.cpp +++ b/tests/TestEntrySearcher.cpp @@ -197,22 +197,22 @@ void TestEntrySearcher::testSearchTermParser() QCOMPARE(terms.length(), 5); - QCOMPARE(terms[0]->field, EntrySearcher::Field::Undefined); - QCOMPARE(terms[0]->word, QString("test")); - QCOMPARE(terms[0]->exclude, true); + QCOMPARE(terms[0].field, EntrySearcher::Field::Undefined); + QCOMPARE(terms[0].word, QString("test")); + QCOMPARE(terms[0].exclude, true); - QCOMPARE(terms[1]->field, EntrySearcher::Field::Undefined); - QCOMPARE(terms[1]->word, QString("quoted \\\"string\\\"")); - QCOMPARE(terms[1]->exclude, false); + QCOMPARE(terms[1].field, EntrySearcher::Field::Undefined); + QCOMPARE(terms[1].word, QString("quoted \\\"string\\\"")); + QCOMPARE(terms[1].exclude, false); - QCOMPARE(terms[2]->field, EntrySearcher::Field::Username); - QCOMPARE(terms[2]->word, QString("user")); + QCOMPARE(terms[2].field, EntrySearcher::Field::Username); + QCOMPARE(terms[2].word, QString("user")); - QCOMPARE(terms[3]->field, EntrySearcher::Field::Password); - QCOMPARE(terms[3]->word, QString("test me")); + QCOMPARE(terms[3].field, EntrySearcher::Field::Password); + QCOMPARE(terms[3].word, QString("test me")); - QCOMPARE(terms[4]->field, EntrySearcher::Field::Undefined); - QCOMPARE(terms[4]->word, QString("noquote")); + QCOMPARE(terms[4].field, EntrySearcher::Field::Undefined); + QCOMPARE(terms[4].word, QString("noquote")); // Test wildcard and regex search terms m_entrySearcher.parseSearchTerms("+url:*.google.com *user:\\d+\\w{2}"); @@ -220,11 +220,11 @@ void TestEntrySearcher::testSearchTermParser() QCOMPARE(terms.length(), 2); - QCOMPARE(terms[0]->field, EntrySearcher::Field::Url); - QCOMPARE(terms[0]->regex.pattern(), QString("^.*\\.google\\.com$")); + QCOMPARE(terms[0].field, EntrySearcher::Field::Url); + QCOMPARE(terms[0].regex.pattern(), QString("^.*\\.google\\.com$")); - QCOMPARE(terms[1]->field, EntrySearcher::Field::Username); - QCOMPARE(terms[1]->regex.pattern(), QString("\\d+\\w{2}")); + QCOMPARE(terms[1].field, EntrySearcher::Field::Username); + QCOMPARE(terms[1].regex.pattern(), QString("\\d+\\w{2}")); // Test custom attribute search terms m_entrySearcher.parseSearchTerms("+_abc:efg _def:\"ddd\""); @@ -232,13 +232,13 @@ void TestEntrySearcher::testSearchTermParser() QCOMPARE(terms.length(), 2); - QCOMPARE(terms[0]->field, EntrySearcher::Field::AttributeValue); - QCOMPARE(terms[0]->word, QString("abc")); - QCOMPARE(terms[0]->regex.pattern(), QString("^efg$")); + QCOMPARE(terms[0].field, EntrySearcher::Field::AttributeValue); + QCOMPARE(terms[0].word, QString("abc")); + QCOMPARE(terms[0].regex.pattern(), QString("^efg$")); - QCOMPARE(terms[1]->field, EntrySearcher::Field::AttributeValue); - QCOMPARE(terms[1]->word, QString("def")); - QCOMPARE(terms[1]->regex.pattern(), QString("ddd")); + QCOMPARE(terms[1].field, EntrySearcher::Field::AttributeValue); + QCOMPARE(terms[1].word, QString("def")); + QCOMPARE(terms[1].regex.pattern(), QString("ddd")); } void TestEntrySearcher::testCustomAttributesAreSearched()