Skip to content

Commit

Permalink
Cleanup code and search widget display
Browse files Browse the repository at this point in the history
* Make search widget larger
* Move regex converter into Tools namespace
* Use QSharedPointer for search terms
  • Loading branch information
droidmonkey committed Jul 9, 2018
1 parent 074aa9e commit c7ffcda
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 50 deletions.
58 changes: 23 additions & 35 deletions src/core/EntrySearcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -106,18 +114,14 @@ bool EntrySearcher::searchEntryImpl(const QString& searchString, Entry* entry)
return true;
}

QList<EntrySearcher::SearchTerm*> EntrySearcher::parseSearchTerms(const QString& searchString)
QList<QSharedPointer<EntrySearcher::SearchTerm> > EntrySearcher::parseSearchTerms(const QString& searchString)
{
auto terms = QList<SearchTerm*>();
// 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<QSharedPointer<SearchTerm> >();

auto results = termParser.globalMatch(searchString);
auto results = m_termParser.globalMatch(searchString);
while (results.hasNext()) {
auto result = results.next();
auto term = new SearchTerm();
QSharedPointer<SearchTerm> term(new SearchTerm());

// Quoted string group
term->word = result.captured(3);
Expand All @@ -129,32 +133,16 @@ QList<EntrySearcher::SearchTerm*> 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);
Expand All @@ -175,7 +163,7 @@ QList<EntrySearcher::SearchTerm*> EntrySearcher::parseSearchTerms(const QString&
} else if (field.startsWith("attach", cs)) {
term->field = Field::Attachment;
} else {
term->field = Field::All;
term->field = Field::Undefined;
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/core/EntrySearcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,19 @@ class Entry;
class EntrySearcher
{
public:
EntrySearcher(bool caseSensitive = false);
explicit EntrySearcher(bool caseSensitive = false);

QList<Entry*> search(const QString& searchString, const Group* group);
QList<Entry*> searchEntries(const QString& searchString, const QList<Entry*>& entries);

void setCaseSensitive(bool state);
bool isCaseSensitive();

private:
bool searchEntryImpl(const QString& searchString, Entry* entry);

enum Field {
All,
Undefined,
Title,
Username,
Password,
Expand All @@ -57,9 +58,10 @@ class EntrySearcher
bool exclude;
};

QList<SearchTerm*> parseSearchTerms(const QString& searchString);
QList<QSharedPointer<SearchTerm> > parseSearchTerms(const QString& searchString);

bool m_caseSensitive;
QRegularExpression m_termParser;

friend class TestEntrySearcher;
};
Expand Down
29 changes: 29 additions & 0 deletions src/core/Tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <QImageReader>
#include <QLocale>
#include <QStringList>
#include <QRegularExpression>
#include <cctype>

#include <QElapsedTimer>
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions src/core/Tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <algorithm>

class QIODevice;
class QRegularExpression;

namespace Tools
{
Expand All @@ -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 <typename RandomAccessIterator, typename T>
RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value)
Expand Down
9 changes: 5 additions & 4 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -238,6 +238,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)

DatabaseWidget::~DatabaseWidget()
{
delete m_EntrySearcher;
}

DatabaseWidget::Mode DatabaseWidget::currentMode() const
Expand Down Expand Up @@ -1067,13 +1068,13 @@ void DatabaseWidget::search(const QString& searchtext)

Group* searchGroup = m_searchLimitGroup ? currentGroup() : m_db->rootGroup();

QList<Entry*> searchResult = EntrySearcher(m_searchCaseSensitive).search(searchtext, searchGroup);
QList<Entry*> 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"));
Expand All @@ -1086,7 +1087,7 @@ void DatabaseWidget::search(const QString& searchtext)

void DatabaseWidget::setSearchCaseSensitive(bool state)
{
m_searchCaseSensitive = state;
m_EntrySearcher->setCaseSensitive(state);
refreshSearch();
}

Expand Down
3 changes: 2 additions & 1 deletion src/gui/DatabaseWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class EditEntryWidget;
class EditGroupWidget;
class Entry;
class EntryView;
class EntrySearcher;
class Group;
class GroupView;
class KeePass1OpenWidget;
Expand Down Expand Up @@ -239,8 +240,8 @@ private slots:
DetailsWidget* m_detailsView;

// Search state
EntrySearcher* m_EntrySearcher;
QString m_lastSearchText;
bool m_searchCaseSensitive;
bool m_searchLimitGroup;

// CSV import state
Expand Down
15 changes: 15 additions & 0 deletions src/gui/SearchWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
Expand All @@ -44,6 +47,18 @@
</item>
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">padding:3px</string>
</property>
Expand Down
10 changes: 3 additions & 7 deletions tests/TestEntrySearcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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}");

Expand All @@ -173,6 +171,4 @@ void TestEntrySearcher::testSearchTermParser()

QCOMPARE(terms[1]->field, EntrySearcher::Username);
QCOMPARE(terms[1]->regex.pattern(), QString("\\d+\\w{2}"));

qDeleteAll(terms);
}

0 comments on commit c7ffcda

Please sign in to comment.