package processing import ( "fmt" "strings" ) // knownKeyAliases maps vendor / feed keys onto canonical standard-field keys. var knownKeyAliases = map[string]string{ "ean": "gtin", "ean13": "gtin", "barcode": "gtin", "sku": "sku", "product_name": "name", "title": "name", "producttitle": "name", "desc": "description", "body": "description", "shortdescription": "description", "product_type": "category", "producttype": "category", "brand_name": "brand", "manufacturer": "brand", "mainimage": "image", "main_image": "image", "image_url": "image", "imageurl": "image", "purchaseprice": "price", "purchase_price": "price", "sellingprice": "price", "netwidth": "width", "netheight": "height", "netdepth": "depth", "netmass": "weight", "weight_kg": "weight", "eprelid": "eprel_id", "stockstatus": "stock_status", "stock": "stock", "spec": "specifications", "specs": "specifications", "specification": "specifications", } // NormalizeMapped flattens aliases, trims strings, drops empty/#text wrappers, // and coerces obvious zero-dimension placeholders to empty (not fake "0"). func NormalizeMapped(mapped, raw map[string]any) map[string]any { out := make(map[string]any) mergeNormalized(out, raw) mergeNormalized(out, mapped) // mapped wins return out } func mergeNormalized(dst, src map[string]any) { if src == nil { return } for k, v := range src { canon := canonicalizeKey(k) nv := normalizeValue(canon, v) if nv == nil { continue } if _, exists := dst[canon]; exists && isEmptyValue(nv) { continue } dst[canon] = nv } } func canonicalizeKey(k string) string { compact := strings.ToLower(strings.TrimSpace(k)) compact = strings.ReplaceAll(compact, "-", "_") compact = strings.ReplaceAll(compact, " ", "_") noUnderscore := strings.ReplaceAll(compact, "_", "") if alias, ok := knownKeyAliases[compact]; ok { return alias } if alias, ok := knownKeyAliases[noUnderscore]; ok { return alias } return compact } func normalizeValue(key string, v any) any { if v == nil { return nil } switch t := v.(type) { case string: s := strings.TrimSpace(t) if s == "" { return nil } if isDimensionKey(key) && isZeroishString(s) { return nil } return SanitizeText(s) case float64: if isDimensionKey(key) && t == 0 { return nil } return t case float32: if isDimensionKey(key) && t == 0 { return nil } return float64(t) case int: if isDimensionKey(key) && t == 0 { return nil } return t case int64: if isDimensionKey(key) && t == 0 { return nil } return t case bool: return t case map[string]any: if text, ok := t["#text"]; ok { return normalizeValue(key, text) } if text, ok := t["text"]; ok { return normalizeValue(key, text) } nested := make(map[string]any, len(t)) for nk, nv := range t { if nn := normalizeValue(canonicalizeKey(nk), nv); nn != nil { nested[canonicalizeKey(nk)] = nn } } if len(nested) == 0 { return nil } return nested case []any: if len(t) == 0 { return nil } out := make([]any, 0, len(t)) for _, item := range t { if nn := normalizeValue(key, item); nn != nil { out = append(out, nn) } } if len(out) == 0 { return nil } return out default: s := strings.TrimSpace(fmt.Sprint(t)) if s == "" || s == "" { return nil } if isDimensionKey(key) && isZeroishString(s) { return nil } return SanitizeText(s) } } func isDimensionKey(key string) bool { switch key { case "width", "height", "depth", "weight", "length", "net_width", "net_height", "net_depth", "net_mass": return true default: return false } } func isZeroishString(s string) bool { s = strings.TrimSpace(strings.ToLower(s)) return s == "0" || s == "0.0" || s == "0,0" || s == "0.00" } func isEmptyValue(v any) bool { if v == nil { return true } switch t := v.(type) { case string: return strings.TrimSpace(t) == "" case map[string]any: return len(t) == 0 case []any: return len(t) == 0 default: return false } }