139 lines
2.9 KiB
Go
139 lines
2.9 KiB
Go
package processing
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// StandardFieldDef is the subset of standard_fields used while filling gaps.
|
||
|
|
type StandardFieldDef struct {
|
||
|
|
Key string
|
||
|
|
DefaultValue string
|
||
|
|
Unit string
|
||
|
|
MappingHints []string
|
||
|
|
}
|
||
|
|
|
||
|
|
// FillMissingStandardFields copies values from mapped/raw (via key + mapping_hints)
|
||
|
|
// or default_value into mapped when an enabled field is empty.
|
||
|
|
func FillMissingStandardFields(mapped, raw map[string]any, fields []StandardFieldDef) map[string]any {
|
||
|
|
out := mapped
|
||
|
|
if out == nil {
|
||
|
|
out = map[string]any{}
|
||
|
|
} else {
|
||
|
|
// Shallow copy so callers can keep the original.
|
||
|
|
cp := make(map[string]any, len(out)+len(fields))
|
||
|
|
for k, v := range out {
|
||
|
|
cp[k] = v
|
||
|
|
}
|
||
|
|
out = cp
|
||
|
|
}
|
||
|
|
for _, f := range fields {
|
||
|
|
key := strings.TrimSpace(f.Key)
|
||
|
|
if key == "" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if valuePresent(out[key]) {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
candidates := make([]string, 0, 1+len(f.MappingHints))
|
||
|
|
candidates = append(candidates, key)
|
||
|
|
for _, h := range f.MappingHints {
|
||
|
|
h = strings.TrimSpace(h)
|
||
|
|
if h != "" {
|
||
|
|
candidates = append(candidates, h)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if v := lookupAny(mapped, candidates...); valuePresent(v) {
|
||
|
|
out[key] = normalizeFilled(v, f.Unit)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if v := lookupAny(raw, candidates...); valuePresent(v) {
|
||
|
|
out[key] = normalizeFilled(v, f.Unit)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if strings.TrimSpace(f.DefaultValue) != "" {
|
||
|
|
out[key] = normalizeFilled(f.DefaultValue, f.Unit)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
func valuePresent(v any) bool {
|
||
|
|
if v == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
switch t := v.(type) {
|
||
|
|
case string:
|
||
|
|
return strings.TrimSpace(t) != ""
|
||
|
|
case []any:
|
||
|
|
return len(t) > 0
|
||
|
|
case map[string]any:
|
||
|
|
return len(t) > 0
|
||
|
|
default:
|
||
|
|
s := strings.TrimSpace(fmt.Sprint(t))
|
||
|
|
return s != "" && s != "<nil>"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func lookupAny(m map[string]any, keys ...string) any {
|
||
|
|
if m == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
lower := map[string]any{}
|
||
|
|
for k, v := range m {
|
||
|
|
lower[strings.ToLower(strings.TrimSpace(k))] = v
|
||
|
|
}
|
||
|
|
for _, k := range keys {
|
||
|
|
lk := strings.ToLower(strings.TrimSpace(k))
|
||
|
|
if v, ok := lower[lk]; ok && valuePresent(v) {
|
||
|
|
return v
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func normalizeFilled(v any, unit string) any {
|
||
|
|
switch t := v.(type) {
|
||
|
|
case string:
|
||
|
|
s := SanitizeText(t)
|
||
|
|
if unit != "" && !strings.Contains(strings.ToLower(s), strings.ToLower(unit)) {
|
||
|
|
// Keep numeric values plain; unit stays on the field definition.
|
||
|
|
return s
|
||
|
|
}
|
||
|
|
return s
|
||
|
|
default:
|
||
|
|
return v
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseHints(v any) []string {
|
||
|
|
switch t := v.(type) {
|
||
|
|
case []string:
|
||
|
|
return t
|
||
|
|
case []any:
|
||
|
|
out := make([]string, 0, len(t))
|
||
|
|
for _, item := range t {
|
||
|
|
if s, ok := item.(string); ok && strings.TrimSpace(s) != "" {
|
||
|
|
out = append(out, strings.TrimSpace(s))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
case string:
|
||
|
|
s := strings.TrimSpace(t)
|
||
|
|
if s == "" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
parts := strings.Split(s, ",")
|
||
|
|
out := make([]string, 0, len(parts))
|
||
|
|
for _, p := range parts {
|
||
|
|
p = strings.TrimSpace(p)
|
||
|
|
if p != "" {
|
||
|
|
out = append(out, p)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
default:
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|