Files
2026-08-16 16:57:36 +02:00

149 lines
3.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package processing
import (
"regexp"
"strings"
)
var (
brandPrefixRe = regexp.MustCompile(`(?i)^([A-Za-z][A-Za-z0-9&.\-]{1,40})\b`)
dimTripleRe = regexp.MustCompile(`(?i)(\d+(?:[.,]\d+)?)\s*[x×]\s*(\d+(?:[.,]\d+)?)\s*[x×]\s*(\d+(?:[.,]\d+)?)`)
dimPairRe = regexp.MustCompile(`(?i)(\d+(?:[.,]\d+)?)\s*[x×]\s*(\d+(?:[.,]\d+)?)`)
weightRe = regexp.MustCompile(`(?i)(\d+(?:[.,]\d+)?)\s*(kg|g|lb|oz)\b`)
)
// FillMissingFields derives sensible standard fields from name/attrs when absent.
func FillMissingFields(mapped map[string]any, attrs map[string]any) map[string]any {
out := make(map[string]any, len(mapped)+8)
for k, v := range mapped {
out[k] = v
}
name := stringFromAny(out["name"])
if name == "" {
name = stringFromAny(out["title"])
}
if stringFromAny(out["brand"]) == "" {
if b := stringFromAny(attrs["brand"]); b != "" {
out["brand"] = b
} else if b := inferBrand(name); b != "" {
out["brand"] = b
}
}
if stringFromAny(out["gtin"]) == "" {
if g := stringFromAny(out["ean"]); g != "" {
out["gtin"] = g
}
}
blob := name + " " + stringFromAny(out["description"])
for _, m := range []map[string]any{attrs, out} {
for _, k := range []string{"dimensions", "size", "dimension"} {
blob += " " + stringFromAny(m[k])
}
}
if stringFromAny(out["width"]) == "" || stringFromAny(out["height"]) == "" || stringFromAny(out["depth"]) == "" {
if w, h, d, ok := parseDimensions(blob); ok {
if stringFromAny(out["width"]) == "" {
out["width"] = w
}
if stringFromAny(out["height"]) == "" {
out["height"] = h
}
if stringFromAny(out["depth"]) == "" && d != "" {
out["depth"] = d
}
}
}
if stringFromAny(out["weight"]) == "" {
if w := stringFromAny(attrs["weight"]); w != "" {
out["weight"] = w
} else if w, ok := parseWeight(blob); ok {
out["weight"] = w
}
}
if c := categoryUniqueIDFromAny(out["category"]); c != "" {
out["category"] = c
} else if c := categoryUniqueIDFromAny(attrs["category"]); c != "" {
out["category"] = c
} else if c := categoryUniqueIDFromAny(out["category_unique_id"]); c != "" {
out["category"] = c
}
if stringFromAny(out["stock_status"]) == "" {
if s := stringFromAny(out["stock"]); s != "" {
out["stock_status"] = normalizeStockStatusLabel(s)
}
} else {
out["stock_status"] = normalizeStockStatusLabel(stringFromAny(out["stock_status"]))
}
return out
}
func inferBrand(name string) string {
name = strings.TrimSpace(name)
if name == "" {
return ""
}
m := brandPrefixRe.FindStringSubmatch(name)
if len(m) < 2 {
return ""
}
b := strings.TrimSpace(m[1])
// Skip generic leading words
switch strings.ToLower(b) {
case "the", "new", "set", "pack", "pair", "product", "item":
return ""
}
return SanitizeOutput(b)
}
func parseDimensions(blob string) (w, h, d string, ok bool) {
if m := dimTripleRe.FindStringSubmatch(blob); len(m) == 4 {
return normalizeNum(m[1]), normalizeNum(m[2]), normalizeNum(m[3]), true
}
if m := dimPairRe.FindStringSubmatch(blob); len(m) == 3 {
return normalizeNum(m[1]), normalizeNum(m[2]), "", true
}
return "", "", "", false
}
func parseWeight(blob string) (string, bool) {
m := weightRe.FindStringSubmatch(blob)
if len(m) < 3 {
return "", false
}
return normalizeNum(m[1]) + " " + strings.ToLower(m[2]), true
}
func normalizeNum(s string) string {
return strings.ReplaceAll(strings.TrimSpace(s), ",", ".")
}
func normalizeStockStatusLabel(s string) string {
if mapped := MapStockStatus(s); mapped != "" {
return mapped
}
s = strings.ToLower(strings.TrimSpace(s))
switch {
case s == "" || s == "0" || strings.Contains(s, "out"):
return "out_of_stock"
case strings.Contains(s, "pre"):
return "preorder"
case strings.Contains(s, "back"):
return "backorder"
default:
return "in_stock"
}
}
func stringFromAny(v any) string {
// Flatten arrays / #text wrappers; callers that need HTML-stripped descriptions
// should use PlainDescriptionFromAny (normalize + V1 enforce do that).
return coerceScalarText(v)
}