[office/tellico] /: New class for parsing filter text

Robby Stephenson null at kde.org
Wed Mar 13 13:11:25 GMT 2024


Git commit 1e129be269abe472517854a77519b618e611b136 by Robby Stephenson.
Committed on 13/03/2024 at 13:11.
Pushed by rstephenson into branch 'master'.

New class for parsing filter text

M  +1    -1    doc/fundamentals.docbook
M  +1    -0    src/CMakeLists.txt
M  +0    -1    src/fetch/execexternalfetcher.cpp
M  +0    -29   src/filter.cpp
M  +0    -2    src/filter.h
A  +87   -0    src/filterparser.cpp     [License: GPL (v2/3)]
C  +23   -11   src/filterparser.h [from: src/tests/filtertest.h - 074% similarity]
M  +7    -20   src/mainwindow.cpp
M  +3    -1    src/tests/CMakeLists.txt
M  +50   -10   src/tests/filtertest.cpp
M  +1    -1    src/tests/filtertest.h

https://invent.kde.org/office/tellico/-/commit/1e129be269abe472517854a77519b618e611b136

diff --git a/doc/fundamentals.docbook b/doc/fundamentals.docbook
index fb8c3e1d1..ecb57c29e 100644
--- a/doc/fundamentals.docbook
+++ b/doc/fundamentals.docbook
@@ -25,7 +25,7 @@ The entries in the collection are grouped together when they share the same valu
 </para>
 
 <para>
-You can use the <interface>Quick Filter</interface> in the toolbar to quickly limit the visible entries to ones which contain the word you type. The filter is also useful when you want to quickly find an entry. You can type in the title or some other unique word that identifies the entry and the <link linkend="detailed-view"><interface>Column View</interface></link> will only show the entries that match the filter. The status bar shows you how many entries are in the collection, and how many are currently being filtered.
+You can use the <interface>Quick Filter</interface> in the toolbar to limit the visible entries. The filter is also useful when you want to quickly find an entry. You can type in the title or some other unique word that identifies the entry and the <link linkend="detailed-view"><interface>Column View</interface></link> will only show the entries that match the filter. More complex filtering can use field names directly, such as <emphasis>author=stephenson</emphasis>. The status bar shows you how many entries are in the collection, and how many are currently being filtered.
 </para>
 
 </sect1>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index faf4606b2..8793d5ae3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -69,6 +69,7 @@ SET(tellico_SRCS
    fieldformat.cpp
    filter.cpp
    filterdialog.cpp
+   filterparser.cpp
    filterview.cpp
    groupview.cpp
    importdialog.cpp
diff --git a/src/fetch/execexternalfetcher.cpp b/src/fetch/execexternalfetcher.cpp
index a0828b136..4da518b03 100644
--- a/src/fetch/execexternalfetcher.cpp
+++ b/src/fetch/execexternalfetcher.cpp
@@ -45,7 +45,6 @@
 #include <KProcess>
 #include <KUrlRequester>
 #include <KAcceleratorManager>
-#include <KShell>
 #include <KConfigGroup>
 
 #include <QLabel>
diff --git a/src/filter.cpp b/src/filter.cpp
index f3f4bc6cc..de413cba8 100644
--- a/src/filter.cpp
+++ b/src/filter.cpp
@@ -316,32 +316,3 @@ bool Filter::operator==(const Filter& other) const {
          m_name == other.m_name &&
          *static_cast<const QList<FilterRule*>*>(this) == static_cast<const QList<FilterRule*>&>(other);
 }
-
-void Filter::populateQuickFilter(FilterPtr filter_, const QString& fieldName_, const QString& text_, bool allowRegExp_) {
-  Q_ASSERT(filter_);
-  if(text_.isEmpty()) return;
-
-  // if the text contains any non-word characters, assume it's a regexp
-  // but \W in qt is letter, number, or '_', I want to be a bit less strict
-  static const QRegularExpression rx(QLatin1String("[^\\w\\s\\-']"));
-  if(allowRegExp_ && rx.match(text_).hasMatch()) {
-    QString text = text_;
-    QRegularExpression tx(text);
-    if(!tx.isValid()) {
-      text = QRegularExpression::escape(text);
-      tx.setPattern(text);
-    }
-    if(tx.isValid()) {
-      filter_->append(new FilterRule(fieldName_, text, FilterRule::FuncRegExp));
-      return;
-    }
-  }
-
-  static const QRegularExpression whiteSpace(QLatin1String("\\s"));
-  // split by whitespace, and add rules for each word
-  const QStringList tokens = text_.split(whiteSpace);
-  foreach(const QString& token, tokens) {
-    // an empty field string means check every field
-    filter_->append(new FilterRule(fieldName_, token, FilterRule::FuncContains));
-  }
-}
diff --git a/src/filter.h b/src/filter.h
index 0d54d1075..68d1a109c 100644
--- a/src/filter.h
+++ b/src/filter.h
@@ -139,8 +139,6 @@ public:
 
   bool operator==(const Filter& other) const;
 
-  static void populateQuickFilter(FilterPtr filter, const QString& fieldName, const QString& text, bool allowRegExp);
-
 private:
   Filter& operator=(const Filter& other);
 
diff --git a/src/filterparser.cpp b/src/filterparser.cpp
new file mode 100644
index 000000000..ff0465c8a
--- /dev/null
+++ b/src/filterparser.cpp
@@ -0,0 +1,87 @@
+/***************************************************************************
+    Copyright (C) 2024 Robby Stephenson <robby at periapsis.org>
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or         *
+ *   modify it under the terms of the GNU General Public License as        *
+ *   published by the Free Software Foundation; either version 2 of        *
+ *   the License or (at your option) version 3 or any later version        *
+ *   accepted by the membership of KDE e.V. (or its successor approved     *
+ *   by the membership of KDE e.V.), which shall act as a proxy            *
+ *   defined in Section 14 of version 3 of the license.                    *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "filterparser.h"
+
+#include <KShell>
+
+using Tellico::FilterParser;
+
+FilterParser::FilterParser(const QString& text_, bool allowRegExp_)
+    : m_text(text_)
+    , m_allowRegExp(allowRegExp_) {
+}
+
+Tellico::FilterPtr FilterParser::filter() {
+  if(m_text.isEmpty()) {
+    return FilterPtr();
+  }
+
+  FilterPtr filter(new Filter(Filter::MatchAll));
+
+  const auto tokens = KShell::splitArgs(m_text);
+  foreach(const QString& token, tokens) {
+    QString fieldName; // empty field name means match on any field
+    QString fieldText;
+    // if the text contains '=' assume it's a field name or title
+    const auto pos = token.indexOf(QLatin1Char('='));
+    if(pos > 0) {
+      fieldName = token.left(pos).trimmed();
+      fieldText = token.mid(pos+1).trimmed();
+      // check that the field name might be a title
+      if(m_coll && !m_coll->hasField(fieldName)) {
+        fieldName = m_coll->fieldNameByTitle(fieldName);
+        // if there's no fieldName match, then use the whole string in the filter
+        if(fieldName.isEmpty()) {
+          fieldText = m_text;
+        }
+      }
+    } else {
+      fieldText = token;
+    }
+    parseToken(filter, fieldName, fieldText);
+  }
+
+  return filter;
+}
+
+void FilterParser::parseToken(FilterPtr filter_, const QString& fieldName_, const QString& fieldText_) {
+  // if the text contains any non-word characters, assume it's a regexp
+  // but \W in qt is letter, number, or '_', I want to be a bit less strict
+  static const QRegularExpression rx(QLatin1String("[^\\w\\s\\-']"));
+  if(m_allowRegExp && rx.match(fieldText_).hasMatch()) {
+    QString text = fieldText_;
+    QRegularExpression tx(text);
+    if(!tx.isValid()) {
+      text = QRegularExpression::escape(text);
+      tx.setPattern(text);
+    }
+    if(tx.isValid()) {
+      filter_->append(new FilterRule(fieldName_, text, FilterRule::FuncRegExp));
+      return;
+    }
+  }
+
+  filter_->append(new FilterRule(fieldName_, fieldText_, FilterRule::FuncContains));
+}
diff --git a/src/tests/filtertest.h b/src/filterparser.h
similarity index 74%
copy from src/tests/filtertest.h
copy to src/filterparser.h
index fcafc0689..0baff69ca 100644
--- a/src/tests/filtertest.h
+++ b/src/filterparser.h
@@ -1,5 +1,5 @@
 /***************************************************************************
-    Copyright (C) 2011 Robby Stephenson <robby at periapsis.org>
+    Copyright (C) 2024 Robby Stephenson <robby at periapsis.org>
  ***************************************************************************/
 
 /***************************************************************************
@@ -22,19 +22,31 @@
  *                                                                         *
  ***************************************************************************/
 
-#ifndef FILTERTEST_H
-#define FILTERTEST_H
+#ifndef TELLICO_FILTERPARSER_H
+#define TELLICO_FILTERPARSER_H
 
-#include <QObject>
+#include "datavectors.h"
 
-class FilterTest : public QObject {
-Q_OBJECT
+namespace Tellico {
 
-private Q_SLOTS:
-  void initTestCase();
-  void testFilter();
-  void testGroupViewFilter();
-  void testQuickFilter();
+/**
+ * @author Robby Stephenson
+ */
+class FilterParser {
+
+public:
+  FilterParser(const QString& text, bool allowRegExp=false);
+
+  void setCollection(Tellico::Data::CollPtr coll_) { m_coll = coll_; }
+  FilterPtr filter();
+
+private:
+  void parseToken(FilterPtr filter, const QString& fieldName, const QString& text);
+
+  QString m_text;
+  Data::CollPtr m_coll;
+  bool m_allowRegExp;
 };
 
+} // end namespace
 #endif
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 76dc2a98a..38c1d482c 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -35,6 +35,7 @@
 #include "entry.h"
 #include "configdialog.h"
 #include "filter.h"
+#include "filterparser.h"
 #include "filterdialog.h"
 #include "collectionfieldsdialog.h"
 #include "controller.h"
@@ -1887,26 +1888,12 @@ void MainWindow::setFilter(const QString& text_) {
     slotClearFilter();
     return;
   }
-  QString text = text_.trimmed();
-  FilterPtr filter;
-  if(!text.isEmpty()) {
-    filter = new Filter(Filter::MatchAll);
-    QString fieldName; // empty field name means match on any field
-    // if the text contains '=' assume it's a field name or title
-    if(text.indexOf(QLatin1Char('=')) > -1) {
-      fieldName = text.section(QLatin1Char('='), 0, 0).trimmed();
-      text = text.section(QLatin1Char('='), 1).trimmed();
-      // check that the field name might be a title
-      if(!Data::Document::self()->collection()->hasField(fieldName)) {
-        fieldName = Data::Document::self()->collection()->fieldNameByTitle(fieldName);
-      }
-    }
-    Filter::populateQuickFilter(filter, fieldName, text, Config::quickFilterRegExp());
-    // also want to update the line edit in case the filter was set by DBUS
-    if(m_quickFilter->text() != text_) {
-      m_quickFilter->setText(text_);
-    }
-  }
+  // update the line edit in case the filter was set by DBUS
+  m_quickFilter->setText(text_);
+
+  FilterParser parser(text_.trimmed(), Config::quickFilterRegExp());
+  parser.setCollection(Data::Document::self()->collection());
+  FilterPtr filter = parser.filter();
   // only update filter if one exists or did exist
   if(filter || m_detailedView->filter()) {
     Controller::self()->slotUpdateFilter(filter);
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 5b3863d25..656404cdc 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -182,8 +182,10 @@ ecm_add_test(entrycomparisontest.cpp
 )
 
 ecm_add_test(filtertest.cpp
+             ../filter.cpp
+             ../filterparser.cpp
     TEST_NAME filtertest
-    LINK_LIBRARIES ${TELLICO_TEST_LIBS} gui
+    LINK_LIBRARIES ${TELLICO_TEST_LIBS}
 )
 
 ecm_add_test(tellicomodeltest.cpp
diff --git a/src/tests/filtertest.cpp b/src/tests/filtertest.cpp
index fa29b593c..e547c75b2 100644
--- a/src/tests/filtertest.cpp
+++ b/src/tests/filtertest.cpp
@@ -27,6 +27,7 @@
 #include "filtertest.h"
 
 #include "../filter.h"
+#include "../filterparser.h"
 #include "../entry.h"
 #include "../collections/bookcollection.h"
 #include "../collections/videocollection.h"
@@ -335,26 +336,65 @@ void FilterTest::testGroupViewFilter() {
   QVERIFY(castFilter.matches(movie));
 }
 
-void FilterTest::testQuickFilter() {
+void FilterTest::testFilterParser() {
   Tellico::Data::CollPtr coll(new Tellico::Data::BookCollection(true, QStringLiteral("TestCollection")));
   Tellico::Data::EntryPtr entry(new Tellico::Data::Entry(coll));
   entry->setField(QStringLiteral("title"), QStringLiteral("C++ Coding Standards"));
+  entry->setField(QStringLiteral("author"), QStringLiteral("Herb Sutter"));
 
-  Tellico::FilterPtr filter(new Tellico::Filter(Tellico::Filter::MatchAll));
-  QString fieldName; // empty means any field
-
-  Tellico::Filter::populateQuickFilter(filter, fieldName, QStringLiteral("C++"), true /* allow regexps */);
+  Tellico::FilterParser parser(QStringLiteral("C++"), true /* allow regexps */);
+  Tellico::FilterPtr filter = parser.filter();
   QVERIFY(filter->matches(entry));
 
   entry->setField(QStringLiteral("title"), QStringLiteral("Coding Standards"));
   QVERIFY(filter->matches(entry)); // still matches due to c++ being interpreted as a regexp
 
-  Tellico::FilterPtr filter2(new Tellico::Filter(Tellico::Filter::MatchAll));
+  Tellico::FilterParser parser2(QStringLiteral("C++"), false /* allow regexps */);
+  filter = parser2.filter();
+  QVERIFY(!filter->matches(entry)); // no longer matches
 
-  Tellico::Filter::populateQuickFilter(filter2, fieldName, QStringLiteral("C++"), false /* allow regexps */);
   entry->setField(QStringLiteral("title"), QStringLiteral("C++ Coding Standards"));
-  QVERIFY(filter2->matches(entry));
+  QVERIFY(filter->matches(entry));
 
-  entry->setField(QStringLiteral("title"), QStringLiteral("Coding Standards"));
-  QVERIFY(!filter2->matches(entry)); // no longer matches
+  // match title fiel donly
+  Tellico::FilterParser parser3(QStringLiteral("title=C++"), false /* allow regexps */);
+  filter = parser3.filter();
+  auto rule0 = filter->at(0);
+  QVERIFY(rule0);
+  QCOMPARE(rule0->fieldName(), QLatin1String("title"));
+  QCOMPARE(rule0->pattern(), QLatin1String("C++"));
+
+  entry->setField(QStringLiteral("title"), QStringLiteral("C++ Coding Standards"));
+  QVERIFY(filter->matches(entry));
+
+  // shouldn't match since there's no field names Title2
+  Tellico::FilterParser parser4(QStringLiteral("Title2=C++"), false /* allow regexps */);
+  parser4.setCollection(coll);
+  filter = parser4.filter();
+  QVERIFY(!filter->matches(entry));
+
+  // match by field title instead
+  Tellico::FilterParser parser5(QStringLiteral("Title=C++"), false /* allow regexps */);
+  parser5.setCollection(coll);
+  filter = parser5.filter();
+  QVERIFY(filter->matches(entry));
+
+  Tellico::FilterParser parser6(QStringLiteral("title=\"C++ coding\""), false /* allow regexps */);
+  filter = parser6.filter();
+  rule0 = filter->at(0);
+  QVERIFY(rule0);
+  QCOMPARE(rule0->fieldName(), QLatin1String("title"));
+  QCOMPARE(rule0->pattern(), QLatin1String("C++ coding"));
+
+  Tellico::FilterParser parser7(QStringLiteral("title=\"C++ coding\" author=sutter"), false /* allow regexps */);
+  filter = parser7.filter();
+  rule0 = filter->at(0);
+  QVERIFY(rule0);
+  QCOMPARE(rule0->fieldName(), QLatin1String("title"));
+  QCOMPARE(rule0->pattern(), QLatin1String("C++ coding"));
+  QVERIFY(filter->size() > 1);
+  auto rule1 = filter->at(1);
+  QVERIFY(rule1);
+  QCOMPARE(rule1->fieldName(), QLatin1String("author"));
+  QCOMPARE(rule1->pattern(), QLatin1String("sutter"));
 }
diff --git a/src/tests/filtertest.h b/src/tests/filtertest.h
index fcafc0689..a33dc3a5b 100644
--- a/src/tests/filtertest.h
+++ b/src/tests/filtertest.h
@@ -34,7 +34,7 @@ private Q_SLOTS:
   void initTestCase();
   void testFilter();
   void testGroupViewFilter();
-  void testQuickFilter();
+  void testFilterParser();
 };
 
 #endif


More information about the kde-doc-english mailing list