carddav: add filtering and matching helper functions
Updates emersion/hydroxide#159. Signed-off-by: Sebastien Binet <binet@cern.ch> Co-authored-by: Conrad Hoffmann <ch@bitfehler.net>
This commit is contained in:
committed by
Simon Ser
parent
dc57b81662
commit
6d59672ed4
146
carddav/match.go
Normal file
146
carddav/match.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package carddav
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-vcard"
|
||||
)
|
||||
|
||||
// Filter returns the filtered list of address objects matching the provided query.
|
||||
// A nil query will return the full list of address objects.
|
||||
func Filter(query *AddressBookQuery, aos []AddressObject) ([]AddressObject, error) {
|
||||
if query == nil {
|
||||
// FIXME: should we always return a copy of the provided slice?
|
||||
return aos, nil
|
||||
}
|
||||
|
||||
n := query.Limit
|
||||
if n <= 0 || n > len(aos) {
|
||||
n = len(aos)
|
||||
}
|
||||
out := make([]AddressObject, 0, n)
|
||||
for _, ao := range aos {
|
||||
ok, err := Match(query, &ao)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO properties are not currently filtered even if requested
|
||||
|
||||
out = append(out, ao)
|
||||
if len(out) >= n {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Match reports whether the provided AddressObject matches the query.
|
||||
func Match(query *AddressBookQuery, ao *AddressObject) (matched bool, err error) {
|
||||
if query == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
switch query.FilterTest {
|
||||
default:
|
||||
return false, fmt.Errorf("unknown query filter test %q", query.FilterTest)
|
||||
|
||||
case FilterAnyOf, "":
|
||||
for _, prop := range query.PropFilters {
|
||||
ok, err := matchPropFilter(prop, ao)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
|
||||
case FilterAllOf:
|
||||
for _, prop := range query.PropFilters {
|
||||
ok, err := matchPropFilter(prop, ao)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func matchPropFilter(prop PropFilter, ao *AddressObject) (bool, error) {
|
||||
// TODO: this only matches first field, there could be multiple
|
||||
field := ao.Card.Get(prop.Name)
|
||||
if field == nil {
|
||||
return prop.IsNotDefined, nil
|
||||
} else if prop.IsNotDefined {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// TODO: handle carddav.PropFilter.Params.
|
||||
if len(prop.TextMatches) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
switch prop.Test {
|
||||
default:
|
||||
return false, fmt.Errorf("unknown property filter test %q", prop.Test)
|
||||
|
||||
case FilterAnyOf, "":
|
||||
for _, txt := range prop.TextMatches {
|
||||
ok, err := matchTextMatch(txt, field)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
|
||||
case FilterAllOf:
|
||||
for _, txt := range prop.TextMatches {
|
||||
ok, err := matchTextMatch(txt, field)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func matchTextMatch(txt TextMatch, field *vcard.Field) (bool, error) {
|
||||
// TODO: handle text-match collation attribute
|
||||
var ok bool
|
||||
switch txt.MatchType {
|
||||
default:
|
||||
return false, fmt.Errorf("unknown textmatch type %q", txt.MatchType)
|
||||
|
||||
case MatchEquals:
|
||||
ok = txt.Text == field.Value
|
||||
|
||||
case MatchContains, "":
|
||||
ok = strings.Contains(field.Value, txt.Text)
|
||||
|
||||
case MatchStartsWith:
|
||||
ok = strings.HasPrefix(field.Value, txt.Text)
|
||||
|
||||
case MatchEndsWith:
|
||||
ok = strings.HasSuffix(field.Value, txt.Text)
|
||||
}
|
||||
|
||||
if txt.NegateCondition {
|
||||
ok = !ok
|
||||
}
|
||||
return ok, nil
|
||||
}
|
Reference in New Issue
Block a user