From 524607be6a3ad9be18f9df5bfc4c0b65646254c0 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 1 Apr 2018 22:47:15 -0400 Subject: [PATCH] Cleanup code and search widget display * Make search widget larger * Move regex converter into Tools namespace * Use QSharedPointer for search terms --- src/core/EntrySearcher.cpp | 58 +++++++++++++++---------------------- src/core/EntrySearcher.h | 8 +++-- src/core/Tools.cpp | 29 +++++++++++++++++++ src/core/Tools.h | 3 ++ src/gui/DatabaseWidget.cpp | 9 +++--- src/gui/DatabaseWidget.h | 3 +- src/gui/SearchWidget.ui | 15 ++++++++++ tests/TestEntrySearcher.cpp | 10 ++----- 8 files changed, 85 insertions(+), 50 deletions(-) diff --git a/src/core/EntrySearcher.cpp b/src/core/EntrySearcher.cpp index b42f0275c7..626c3c2d71 100644 --- a/src/core/EntrySearcher.cpp +++ b/src/core/EntrySearcher.cpp @@ -19,9 +19,12 @@ #include "EntrySearcher.h" #include "core/Group.h" +#include "core/Tools.h" -EntrySearcher::EntrySearcher(bool caseSensitive) : - m_caseSensitive(caseSensitive) +EntrySearcher::EntrySearcher(bool caseSensitive) + : m_caseSensitive(caseSensitive) + , m_termParser(R"re(([-*+]+)?(?:(\w*):)?(?:(?=")"((?:[^"\\]|\\.)*)"|([^ ]*))( |$))re") + // Group 1 = modifiers, Group 2 = field, Group 3 = quoted string, Group 4 = unquoted string { } @@ -58,16 +61,21 @@ void EntrySearcher::setCaseSensitive(bool state) m_caseSensitive = state; } -bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry) +bool EntrySearcher::isCaseSensitive() { - auto searchTerms = parseSearchTerms(searchString); - bool found; + return m_caseSensitive; +} +bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry) +{ // Pre-load in case they are needed auto attributes = QStringList(entry->attributes()->keys()); auto attachments = QStringList(entry->attachments()->keys()); - for (SearchTerm* term : searchTerms) { + bool found; + auto searchTerms = parseSearchTerms(searchString); + + for (const auto& term : searchTerms) { switch (term->field) { case Field::Title: found = term->regex.match(entry->resolvePlaceholder(entry->title())).hasMatch(); @@ -106,18 +114,14 @@ bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry) return true; } -QList EntrySearcher::parseSearchTerms(const QString& searchString) +QList > EntrySearcher::parseSearchTerms(const QString& searchString) { - auto terms = QList(); - // Group 1 = modifiers, Group 2 = field, Group 3 = quoted string, Group 4 = unquoted string - auto termParser = QRegularExpression(R"re(([-*+]+)?(?:(\w*):)?(?:(?=")"((?:[^"\\]|\\.)*)"|([^ ]*))( |$))re"); - // Escape common regex symbols except for *, ?, and | - auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re"); + auto terms = QList >(); - auto results = termParser.globalMatch(searchString); + auto results = m_termParser.globalMatch(searchString); while (results.hasNext()) { auto result = results.next(); - auto term = new SearchTerm(); + QSharedPointer term(new SearchTerm()); // Quoted string group term->word = result.captured(3); @@ -129,32 +133,16 @@ QList EntrySearcher::parseSearchTerms(const QString& // If still empty, ignore this match if (term->word.isEmpty()) { - delete term; continue; } - QString regex = term->word; - - // Wildcard support (*, ?, |) - if (!result.captured(1).contains("*")) { - regex.replace(regexEscape, "\\\\1"); - regex.replace("**", "*"); - regex.replace("*", ".*"); - regex.replace("?", "."); - } - - term->regex = QRegularExpression(regex); - if (!m_caseSensitive) { - term->regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - } + auto mods = result.captured(1); - // Exact modifier - if (result.captured(1).contains("+")) { - term->regex.setPattern("^" + term->regex.pattern() + "$"); - } + // Convert term to regex + term->regex = Tools::convertToRegex(term->word, !mods.contains("*"), mods.contains("+"), m_caseSensitive); // Exclude modifier - term->exclude = result.captured(1).contains("-"); + term->exclude = mods.contains("-"); // Determine the field to search QString field = result.captured(2); @@ -175,7 +163,7 @@ QList EntrySearcher::parseSearchTerms(const QString& } else if (field.startsWith("attach", cs)) { term->field = Field::Attachment; } else { - term->field = Field::All; + term->field = Field::Undefined; } } diff --git a/src/core/EntrySearcher.h b/src/core/EntrySearcher.h index 24172c6466..3a2fcb1234 100644 --- a/src/core/EntrySearcher.h +++ b/src/core/EntrySearcher.h @@ -28,18 +28,19 @@ class Entry; class EntrySearcher { public: - EntrySearcher(bool caseSensitive = false); + explicit EntrySearcher(bool caseSensitive = false); QList search(const QString& searchString, const Group* group); QList searchEntries(const QString& searchString, const QList& entries); void setCaseSensitive(bool state); + bool isCaseSensitive(); private: bool searchEntryImpl(const QString& searchString, Entry* entry); enum Field { - All, + Undefined, Title, Username, Password, @@ -57,9 +58,10 @@ class EntrySearcher bool exclude; }; - QList parseSearchTerms(const QString& searchString); + QList > parseSearchTerms(const QString& searchString); bool m_caseSensitive; + QRegularExpression m_termParser; friend class TestEntrySearcher; }; diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 458d429888..3211190e49 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -346,4 +347,32 @@ namespace Tools return bSuccess; } +// Escape common regex symbols except for *, ?, and | +auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re"); + +QRegularExpression convertToRegex(const QString& string, bool useWildcards, bool exactMatch, bool caseSensitive) +{ + QString pattern = string; + + // Wildcard support (*, ?, |) + if (useWildcards) { + pattern.replace(regexEscape, "\\\\1"); + pattern.replace("**", "*"); + pattern.replace("*", ".*"); + pattern.replace("?", "."); + } + + // Exact modifier + if (exactMatch) { + pattern = "^" + pattern + "$"; + } + + auto regex = QRegularExpression(pattern); + if (!caseSensitive) { + regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + } + + return regex; +} + } // namespace Tools diff --git a/src/core/Tools.h b/src/core/Tools.h index 9fd4979957..c7833d28a7 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -28,6 +28,7 @@ #include class QIODevice; +class QRegularExpression; namespace Tools { @@ -44,6 +45,8 @@ namespace Tools void disableCoreDumps(); void setupSearchPaths(); bool createWindowsDACL(); + QRegularExpression convertToRegex(const QString& string, bool useWildcards = false, bool exactMatch = false, + bool caseSensitive = false); template RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index b4f5f8f100..46666ad328 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -220,7 +220,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) m_fileWatchUnblockTimer.setSingleShot(true); m_ignoreAutoReload = false; - m_searchCaseSensitive = false; + m_EntrySearcher = new EntrySearcher(false); m_searchLimitGroup = config()->get("SearchLimitGroup", false).toBool(); #ifdef WITH_XC_SSHAGENT @@ -238,6 +238,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent) DatabaseWidget::~DatabaseWidget() { + delete m_EntrySearcher; } DatabaseWidget::Mode DatabaseWidget::currentMode() const @@ -1067,13 +1068,13 @@ void DatabaseWidget::search(const QString& searchtext) Group* searchGroup = m_searchLimitGroup ? currentGroup() : m_db->rootGroup(); - QList searchResult = EntrySearcher(m_searchCaseSensitive).search(searchtext, searchGroup); + QList searchResult = m_EntrySearcher->search(searchtext, searchGroup); m_entryView->displaySearch(searchResult); m_lastSearchText = searchtext; // Display a label detailing our search results - if (searchResult.size() > 0) { + if (!searchResult.isEmpty()) { m_searchingLabel->setText(tr("Search Results (%1)").arg(searchResult.size())); } else { m_searchingLabel->setText(tr("No Results")); @@ -1086,7 +1087,7 @@ void DatabaseWidget::search(const QString& searchtext) void DatabaseWidget::setSearchCaseSensitive(bool state) { - m_searchCaseSensitive = state; + m_EntrySearcher->setCaseSensitive(state); refreshSearch(); } diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index ed669cdfd0..89045e920f 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -38,6 +38,7 @@ class EditEntryWidget; class EditGroupWidget; class Entry; class EntryView; +class EntrySearcher; class Group; class GroupView; class KeePass1OpenWidget; @@ -240,8 +241,8 @@ private slots: DetailsWidget* m_detailsView; // Search state + EntrySearcher* m_EntrySearcher; QString m_lastSearchText; - bool m_searchCaseSensitive; bool m_searchLimitGroup; // CSV import state diff --git a/src/gui/SearchWidget.ui b/src/gui/SearchWidget.ui index 1583ebe964..ebafdd906f 100644 --- a/src/gui/SearchWidget.ui +++ b/src/gui/SearchWidget.ui @@ -34,6 +34,9 @@ Qt::Horizontal + + QSizePolicy::Minimum + 40 @@ -44,6 +47,18 @@ + + + 0 + 0 + + + + + 0 + 0 + + padding:3px diff --git a/tests/TestEntrySearcher.cpp b/tests/TestEntrySearcher.cpp index 779f933e26..6b74967cfe 100644 --- a/tests/TestEntrySearcher.cpp +++ b/tests/TestEntrySearcher.cpp @@ -144,11 +144,11 @@ void TestEntrySearcher::testSearchTermParser() QCOMPARE(terms.length(), 5); - QCOMPARE(terms[0]->field, EntrySearcher::All); + QCOMPARE(terms[0]->field, EntrySearcher::Undefined); QCOMPARE(terms[0]->word, QString("test")); QCOMPARE(terms[0]->exclude, true); - QCOMPARE(terms[1]->field, EntrySearcher::All); + QCOMPARE(terms[1]->field, EntrySearcher::Undefined); QCOMPARE(terms[1]->word, QString("quoted \\\"string\\\"")); QCOMPARE(terms[1]->exclude, false); @@ -158,11 +158,9 @@ void TestEntrySearcher::testSearchTermParser() QCOMPARE(terms[3]->field, EntrySearcher::Password); QCOMPARE(terms[3]->word, QString("test me")); - QCOMPARE(terms[4]->field, EntrySearcher::All); + QCOMPARE(terms[4]->field, EntrySearcher::Undefined); QCOMPARE(terms[4]->word, QString("noquote")); - qDeleteAll(terms); - // Test wildcard and regex search terms terms = m_entrySearcher.parseSearchTerms("+url:*.google.com *user:\\d+\\w{2}"); @@ -173,6 +171,4 @@ void TestEntrySearcher::testSearchTermParser() QCOMPARE(terms[1]->field, EntrySearcher::Username); QCOMPARE(terms[1]->regex.pattern(), QString("\\d+\\w{2}")); - - qDeleteAll(terms); }