Finding Data Patterns Using Regular Expressions in Qt

Finding Data Patterns Using Regular Expressions in Qt

By Steve Holcomb

Regular Expressions are tools for finding patterns in data. They are an extremely powerful tools, but are also full of arcane rules and cryptic combinations of symbols. Good user interface (UI) design can use Regular Expressions, but shouldn’t require the end-user to enter the Regular Expressions themselves. A well-designed interface can allow the end-user to quickly and easily use Regular Expressions when searching, sorting and filtering data.

Here’s an example, a short application that demonstrates one use for Regular Expressions. (It is a Qt project, which can be compiled and built inside a version of QtCreator that supports Qt 5.12 or newer.)

It shows one way to use Regular Expressions without forcing the end-user to type them into a box. The source data contains a list, supplied by some sort of record-keeping process. The program also contains a drop-down box with some selections that will filter the list.

Choosing a Filter in QML View

The QML file that defines the view in the screenshot uses a ListView to show data. It retrieves the data from an object called 'proxyWrapper'. 

   ListView {
       model: proxyWrapper
       delegate: Rectangle {
           border.color: "black"
           color: "white"
           width: mainListView.width
           height: 36
           Text {
               anchors.centerIn: parent
               font.pixelSize: 18
               text: model.display
           }
       }
   }

The 'proxyWrapper' object is a C++ class that is based on QSortFilterProxyModel. 

Since the release of Qt 5.12, the QSortFilterProxyModel has supported filtering by QRegularExpression. The example in the screenshot above has several options in the ComboBox entry. Selecting an entry calls the function to filter the QSortFilterProxyModel data by the relevant Regular Expression.

ComboBox {
       id: selectionBox
       textRole: "key"
       model: ListModel {
           id: model
           ListElement { key: ""; value: "" }
           ListElement { 
                key: "Year-Month pattern, year 2010 to 2019"; 
                value: "^201\\d.\\w\\w\\w" }
           ListElement { 
               key: "Value-[in|cm] pattern"; 
               value : "\\d+\\.\\d+\\ in|cm" }
       }
       onActivated: {
           proxyWrapper.setFilterRegularExpression(model.get(currentIndex).value)
       }

Presenting Data to the QML View

This example was created with Qt, and loads a QML view to show the data. The data is simulated, but is a good example of output from some sort of logging tool. Each line has a date in the same format, a value, and a unit.

To present the data to the QML, a FilterProxyWrapper was created. It inherits from QSortFilterProxyModel. The FilterProxyWrapper class has a slot that overrides the setFilterRegularExpression slot in the QSortFilterProxyModel.

class FilterProxyWrapper : public QSortFilterProxyModel
{
   Q_OBJECT
public:
   explicit FilterProxyWrapper(QObject *parent = nullptr);

public slots:
   void setFilterRegularExpression(const QString & pattern);
};

The slot performs a validity-check, and sets a case-sensitivity option, on the QRegularExpression. It does allow for other checks, if the developer chooses to add them.

void FilterProxyWrapper::setFilterRegularExpression(const QString & pattern)
{
   QRegularExpression re(pattern, QRegularExpression::CaseInsensitiveOption);
   if (re.isValid()) {
       QSortFilterProxyModel::setFilterRegularExpression(pattern);
   }
}

Exposing FilterProxyWrapper to the QML View

The above code would not work by itself. In the main() function of the executable, the objects need to be exposed to the QML context. Also, the FilterProxyWrapper object needs to be provided with a Model containing data. Finally, the Model needs to be populated with the simulated data.

This snippet from main() shows all of that happening. It also shows the call to load the QML file.

int main(int argc, char *argv[])
{
   /* ... */ 
   QQmlApplicationEngine engine;
   QQmlContext *context = engine.rootContext();
   QStandardItemModel listModel;
   populateModel(listModel, rdm);
   FilterProxyWrapper proxyWrapper;
   proxyWrapper.setSourceModel(&listModel);
   context->setContextProperty(QStringLiteral("proxyWrapper"), &proxyWrapper);

   engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
   if (engine.rootObjects().isEmpty())
       return -1;

   return app.exec();
}

Examples of Filtering Options

One option is to filter according to a particular span of years. Selecting that option shows the following. 

The Regular Expression shown is intended to match any string beginning with “201”, followed by any number, then by any character at all, then by three characters defining a word of some kind.

With this data-pattern, other options are possible. Another example would be to filter for lines that contain some decimal number, followed by the “in” or “cm” measurements.

If the source-data is in a predictable pattern, then it is easy for a programmer to generate a list of useful RegularExpressions. The end-user can be given lots of options to filter/sort the data from a drop-down.

Summary

This example shows one use of QRegularExpressions in filtering data. It also shows ways that a UI designer can give the end-user some of these abilities, even if the end-user doesn’t know much about Regular Expressions.

A well-designed app that handles and displays data can use Regular Expressions in the way shown here. The end-user can be empowered to rapidly and easily filter a large set of data according to a few simple rules.

References

1. https://en.wikipedia.org/wiki/Regular_expression

2. http://doc.qt.io/qt-5/qregularexpression.html