Multi-Parameter-Filter

Ein Multi-Parameter-Filter. Bool’sche Algebra im Einsatz.

in dem heutigen Artikel möchte ich einen Algorithmus vorstellen, der es ermöglicht gegen Multi-Parameter-Regeln zu entscheiden. Was das genau heißt wird vielleicht mit einem Use-Case klarer.

Im Jahre 2005 habe ich einen Server-Dienst geschrieben, der unter anderem Emails von verschiedenen Email-Konten abholt und automatisiert untersucht. Ein Teil dieser Entwicklung stellt ein Email-Filter dar, der per Webfrontend konfigurierbar ist und der Usern ermöglicht, Regeln zu definieren an Hand dessen der Server-Dienst entscheiden kann, ob die zu prüfende Email relevant ist oder nicht. Eine Regel könnte beispielsweise aussehen wie „Wenn der Empfänger mit ‚joe’ beginnt und im Subject die Worte ‚Hallo’ und ‚Welt’ enthalten sind, dann ignoriere diese Email nicht.” [1].

 Folgende Fälle wollen wir unterscheiden:

  • Empfänger beginnt mit Wort,
  • Empfänger beinhaltet Wort,
  • Empfänger endet mit Wort,
  • Subject ist ungleich Wortliste,
  • Subject enthält mindestens eins der Worte aus Wortliste,
  • Subject enthält alle Worte aus Wortliste,
  • Mit der Erfüllung der Regel ist Email gültig oder ungültig

Man liest schon eine Portion boolescher Algebra raus. Man kann hier aber auch schon sehen, dass ein solches Filter-Konstrukt auf andere Fälle und Anwendungen einfach übertragbar sein wird, denn überall wo man gegen Parameter prüft um zu filtern, können zumindest Teile aus meinem Algorithmus hilfreich sein. Herzstück hierbei wird die Prüfung des „Subjects” also die Prüfung gegen eine Wortliste sein, wo wir uns einem kleinen Trick bedienen um die Sache schön übersichtlich zu halten.

Starten wir direkt in VS2005 und erstellen eine neue Windows-Anwendung. Als erstes brauchen wir ein Formular wie in der Abbildung zu sehen.

initiales Hauptformular
initiales Hauptformular

In den ersten beiden Feldern (txt_Email, txt_Subject) tragen wir die Email-Adresse und den Betreff ein gegen den wir prüfen wollen.

Die Email- und Subject-Phrase bestehen dagegen aus den aus der Regel definierten Prüfwerten. Also die Werte gegen die wir die Feldern txt_Email und txt_Subject evaluieren wollen.

Die in den Dropdown-Listen beheimateten Modes geben uns nun die Möglichkeit die Art der Prüfung zu definieren. Mit dem EmailMode wird festgelegt ob txt_Email mit der Email-Phrase beginnen soll (0), diese beinhalten soll (1) oder damit enden soll (2).

Der SubjectMode dagegen prüft gegen eine Wortliste, da die Subject-Phrase aus einer Semikolon-Separierten Liste bestehen kann. Daher prüfen wir hier ob es ein exakter Match sein soll (0), mindestens eins der Worte im Subject vorkommen soll (1) oder ob alle Worte vorhanden sein sollen (2).

Mit dem NotMode schließlich kann bestimmt werden, ob die mit den anderen definierten Parametern eine Ausschluß- oder Einschlußregel ist. D.h. ob die definierte Regel eine Email-Nachricht qualifiziert oder nicht.

Der dem Filter zugrunde liegende Algorithmus besteht, wie durch die Modes erkennbar, aus drei Teilen.

Prüfung der Email-Adresse. Hier gibt uns .Net eigentlich alles was wir brauche mit, sodass wir hier nur einen einfachen Select Case einsetzen müssen.

If Len(_sEmailPhrase) > 0 Then
    Select Case _iEmailMode
        Case 0
            bEmail = CBool(LCase(_sEmail).StartsWith(LCase(_sEmailPhrase)))
        Case 1
            bEmail = CBool(InStr(LCase(_sEmail), LCase(_sEmailPhrase)))
        Case 2
            bEmail = CBool(LCase(_sEmail).EndsWith(LCase(_sEmailPhrase)))
    End Select
End If

Prüfung des Subject. Hier müssen wir ein paar Fälle unterscheiden. Zum einen kann die SubjectPhrase eine Semikolon-Separierte Liste sein, muss es aber nicht. Wenn es eine Liste ist, dann müssen die einzelnen Worte gegen das Subject geprüft werden und entweder mit AND oder OR ausgewertet werden. Um diese boolschen Funktionen im Kontext besser auszuwerten habe ich zwei einfache Funktionen benutzt:

Public Function ConcatenateWithAnd(ByVal bArgs() As Boolean) As Boolean
    If bArgs(0) And bArgs(1) Then Return True Else Return False
End Function
 
Public Function ConcatenateWithOr(ByVal bArgs() As Boolean) As Boolean
    If bArgs(0) Or bArgs(1) Then Return True Else Return False
End Function

Eine Subject-Prüfung muss logischerweise nur erfolgen, wenn die Email-Prüfung abgeschlossen wurde und ein Ergebnis vorliegt das eine weitere Prüfung nötig macht. Daher kommt eine If-Bedingung um die Subject-Prüfung.

If bEmail Then
    If _iSubjectMode = 0 Then sSeperator = ""
    sSubjectWords = Split(_sSubjectPhrase, sSeperator)
 
    If UBound(sSubjectWords) = 0 Then _iSubjectMode = 0
 
    If _iSubjectMode < 2 Then sConcatenator = "ConcatenateWithOr" : bReturn = False
 
    If Len(_sSubjectPhrase) > 0 Then
        For iCounter = 0 To UBound(sSubjectWords)
            If sConcatenator = "ConcatenateWithAnd" And bReturn = False Then Exit For
            If sConcatenator = "ConcatenateWithOr" And bReturn = True Then Exit For
            bReturn = CallByName( _
                            Me, _
                            sConcatenator, _
                            CallType.Method, _
                                        New Boolean(1) { _
                                            CBool(InStr(LCase(_sSubject), LCase(sSubjectWords(iCounter)))), _
                                            bReturn _
                                        } _
                      )
        Next
    Else
        bReturn = True
    End If
 
    'to be done: consider Not-Mode
Else
    'to be done: consider Not-Mode
End If

Der Trick den ich hier angewendet habe ist, dass ich je nach Mode-Auswahl die von mir oben definierten einfachen booleschen Funktionen mit einem CallByName aufrufe. Dadurch kann ich recht elegant die Wortliste durchlaufen. Zudem kann ich die Schleife recht einfach kontrollieren und bei einer AND-Verknüpfung und einem Zwischenergebnis von „false” die Schleife abbrechen, da eine AND-Verknüpfung mit „false” immer „false” ergeben wird. Ebenso kann ich bei OR verfahren, da bei einem Zwischenergebnis von „true” bei einer OR-Verknüfung immer „true” herauskommen wird.

Prüfung der Negierung. OK, das ist trivial und bedarf eigentlich keiner weiteren Erklärung.

Es muss nur noch beachtet werden, dass die Not-Mode-Prüfung in jeweils dem If- und auch dem Else-Zweig geprüft werden muss, jedoch auf zwei unterschiedlichen Wegen.

Im If-Zweig negieren wir das Zwischenergebnis, also

If _iNotMode Then bReturn = Not bReturn

Dagegen prüfen wir im Else-Zweig

If _iNotMode Then bEmail = Not bEmail

Wenn man die drei Teile zusammensetzt und verbindet erhält man das Endergebnis mit

bReturn = bEmail And bReturn

Zwei Beispiele zur Verifizierung des Filters mit den folgenden Regeln:

Beispiel 2
Beispiel 2
Beispiel 1
Beispiel 1

„Email beachten, wenn die Email-Adresse mit „joe” anfängt und die Worte „Hallo” und „Hello” im Subject beinhaltet sind.” (Beispiel 1)

und

„Email nicht beachten, wenn die Email-Adresse das Wort „devstack” beinhaltet und das Subject „Das ist ein Test” lautet.” (Beispiel 2, In dem abgebildeten Fall wird die ankommende Email also beachtet, da sie nicht der Regel entspricht, daher Result: true, würde der Betreff von „ein Test” auf „kein Test” geändert, wäre das Result: false)

Das soll zur Theorie genügen. Unten könnt ihr das Beispiel-Projekt runterladen und selber probieren.

Ergebnis. Ich habe diesen Filter-Ansatz aus zweierlei Gründen vorgestellt. Zum einen stellt es ein flexibles Konstrukt für allerlei Filter dar und ist damit leicht auf andere Probleme transferierbar und zum anderen ist es meiner Ansicht nach eine smarte Lösung, wie sie mir gefällt. Wenig Code, übersichtlich, flexibel und entbehrt nicht einer gewissen Ästhetik. Zudem ist mit diesem Ansatz auch möglich mehrere Regeln einfach in Reihe zu schalten.

Das Beispiel-Projekt für VS2005 mit dem fehlenden Rest Logik drum herum könnt ihr hier herunterladen.


[1] In dem Artikel werde ich mich nur auf Email-Adresse und Subject begrenzen um das Beispiel übersichtlich zu halten.