photoprism/internal/form/serialize.go

191 lines
4.9 KiB
Go
Raw Normal View History

package form
import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
"unicode"
"github.com/araddon/dateparse"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt"
)
// Serialize returns a string containing all non-empty fields and values of a struct.
func Serialize(f interface{}, all bool) string {
v := reflect.ValueOf(f)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return ""
}
q := make([]string, 0, v.NumField())
// Iterate through all form fields.
for i := 0; i < v.NumField(); i++ {
fieldValue := v.Field(i)
fieldName := v.Type().Field(i).Tag.Get("form")
fieldInfo := v.Type().Field(i).Tag.Get("serialize")
// Serialize field values as string.
if fieldName != "" && fieldName != "-" && (fieldInfo != "-" || all) {
switch t := fieldValue.Interface().(type) {
case time.Time:
if val := fieldValue.Interface().(time.Time); !val.IsZero() {
if val.Hour() == 0 && val.Minute() == 0 {
q = append(q, fmt.Sprintf("%s:%s", fieldName, val.Format("2006-01-02")))
} else {
q = append(q, fmt.Sprintf("%s:\"%s\"", fieldName, val.String()))
}
}
case int, int8, int16, int32, int64:
if val := fieldValue.Int(); val != 0 {
q = append(q, fmt.Sprintf("%s:%d", fieldName, val))
}
case uint, uint8, uint16, uint32, uint64:
if val := fieldValue.Uint(); val != 0 {
q = append(q, fmt.Sprintf("%s:%d", fieldName, val))
}
case float32, float64:
if val := fieldValue.Float(); val != 0 {
q = append(q, fmt.Sprintf("%s:%f", fieldName, val))
}
case string:
if val := strings.ReplaceAll(fieldValue.String(), "\"", ""); val != "" {
if strings.ContainsAny(val, " :'()[]-+`") {
q = append(q, fmt.Sprintf("%s:\"%s\"", fieldName, val))
} else {
q = append(q, fmt.Sprintf("%s:%s", fieldName, val))
}
}
case bool:
if val := fieldValue.Bool(); val {
q = append(q, fmt.Sprintf("%s:%t", fieldName, fieldValue.Bool()))
}
default:
log.Warnf("form: failed serializing %T %s", t, clean.Token(fieldName))
}
}
}
return strings.Join(q, " ")
}
func Unserialize(f SearchForm, q string) (result error) {
var key, value []rune
var escaped, isKeyValue bool
v := reflect.ValueOf(f)
formValues := v.Elem()
n := formValues.NumField()
fieldNames := make(map[string]string, n)
// Iterate through all form fields.
for i := 0; i < formValues.NumField(); i++ {
fieldName := formValues.Type().Field(i).Name
formName := strings.ToLower(formValues.Type().Field(i).Tag.Get("form"))
formSerialize := strings.ToLower(formValues.Type().Field(i).Tag.Get("serialize"))
if fieldName == "" || formSerialize == "-" {
continue
} else if formName == "" {
formName = strings.ToLower(fieldName)
}
fieldNames[formName] = fieldName
}
f.SetQuery("")
q = strings.TrimSpace(q) + "\n"
var queryStrings []string
for _, char := range q {
if unicode.IsSpace(char) && !escaped {
if isKeyValue {
formName := strings.ToLower(string(key))
fieldName := fieldNames[formName]
field := formValues.FieldByNameFunc(func(name string) bool {
return strings.EqualFold(name, fieldName)
})
stringValue := string(value)
if fieldName != "" && fieldName != "-" && field.CanSet() {
switch field.Interface().(type) {
case time.Time:
if timeValue, err := dateparse.ParseAny(stringValue); err != nil {
result = err
} else {
field.Set(reflect.ValueOf(timeValue))
}
case float32, float64:
if floatValue, err := strconv.ParseFloat(stringValue, 64); err != nil {
result = err
} else {
field.SetFloat(floatValue)
}
case int, int8, int16, int32, int64:
if intValue, err := strconv.Atoi(stringValue); err != nil {
result = err
} else {
field.SetInt(int64(intValue))
}
case uint, uint8, uint16, uint32, uint64:
if intValue, err := strconv.Atoi(stringValue); err != nil {
result = err
} else {
field.SetUint(uint64(intValue))
}
case string:
field.SetString(clean.SearchString(stringValue))
case bool:
field.SetBool(txt.Bool(stringValue))
default:
result = fmt.Errorf("unsupported type: %s", formName)
}
} else {
result = fmt.Errorf("unknown filter: %s", formName)
}
} else if len(strings.TrimSpace(string(key))) > 0 {
queryStrings = append(queryStrings, strings.TrimSpace(string(key)))
}
escaped = false
isKeyValue = false
key = key[:0]
value = value[:0]
} else if char == ':' && !escaped {
isKeyValue = true
} else if char == '"' {
escaped = !escaped
} else if isKeyValue {
value = append(value, char)
} else {
key = append(key, unicode.ToLower(char))
}
}
if len(queryStrings) > 0 {
f.SetQuery(clean.SearchQuery(strings.Join(queryStrings, " ")))
}
if result != nil {
log.Warnf("form: failed parsing search query")
}
return result
}