mirror of
https://github.com/1f349/go-webdav.git
synced 2024-12-22 16:24:14 +00:00
147 lines
2.9 KiB
Go
147 lines
2.9 KiB
Go
|
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
|
||
|
}
|