fix
This commit is contained in:
@@ -44,8 +44,16 @@ func parseSpecsInto(dst map[string]any, v any) {
|
||||
// Already structured attributes / grouped specs
|
||||
if looksLikeAttrMap(t) {
|
||||
for k, val := range t {
|
||||
lk := strings.ToLower(strings.TrimSpace(k))
|
||||
if isReservedProductKey(lk) || isInvalidAttributeKey(k) {
|
||||
continue
|
||||
}
|
||||
if s := stringifySpecValue(val); s != "" {
|
||||
dst[SanitizeOutput(k)] = s
|
||||
key := SanitizeOutput(k)
|
||||
if key == "" || isInvalidAttributeKey(key) {
|
||||
continue
|
||||
}
|
||||
dst[key] = s
|
||||
} else if nested, ok := val.(map[string]any); ok {
|
||||
parseSpecsInto(dst, nested)
|
||||
}
|
||||
@@ -58,7 +66,7 @@ func parseSpecsInto(dst map[string]any, v any) {
|
||||
parseSpecsInto(dst, val)
|
||||
continue
|
||||
}
|
||||
if s := stringifySpecValue(val); s != "" && !isReservedProductKey(lk) {
|
||||
if s := stringifySpecValue(val); s != "" && !isReservedProductKey(lk) && !isInvalidAttributeKey(k) {
|
||||
dst[SanitizeOutput(k)] = s
|
||||
}
|
||||
}
|
||||
@@ -105,15 +113,110 @@ func looksLikeAttrMap(m map[string]any) bool {
|
||||
}
|
||||
|
||||
func isReservedProductKey(k string) bool {
|
||||
switch k {
|
||||
case "name", "title", "description", "gtin", "ean", "brand", "category",
|
||||
"price", "image", "stock", "eprel_id", "specifications", "raw", "mapped":
|
||||
switch strings.ToLower(strings.TrimSpace(k)) {
|
||||
case "name", "title", "description", "gtin", "ean", "category", "category_unique_id",
|
||||
"price", "purchaseprice", "purchase_price", "sellingprice", "selling_price",
|
||||
"image", "main_image", "mainimage", "moreimages", "more_images", "images",
|
||||
"image_url", "imageurl", "image_link", "imagelink", "additional_image_urls",
|
||||
"additional_image_link", "videourl", "video_url",
|
||||
"stock", "stockstatus", "stock_status", "availability",
|
||||
"id", "sku", "officiallink", "official_link", "service",
|
||||
"specifications", "specs", "specification",
|
||||
"raw", "mapped", "search":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isInvalidAttributeKey rejects garbage keys from bad feed specs (e.g. ":").
|
||||
func isInvalidAttributeKey(k string) bool {
|
||||
k = strings.TrimSpace(k)
|
||||
if k == "" || k == ":" || k == "=" || k == "-" || k == "_" {
|
||||
return true
|
||||
}
|
||||
// Must contain at least one letter after sanitize.
|
||||
hasLetter := false
|
||||
for _, r := range strings.ToLower(k) {
|
||||
if r >= 'a' && r <= 'z' {
|
||||
hasLetter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return !hasLetter
|
||||
}
|
||||
|
||||
// SanitizeProductAttributes keeps characteristic attrs for API/storage and drops
|
||||
// core product fields that belong on the V1 item root (title, description, images, …).
|
||||
func SanitizeProductAttributes(attrs map[string]any) map[string]any {
|
||||
return sanitizeProductAttributes(attrs, false)
|
||||
}
|
||||
|
||||
// SanitizeV1ProcessAttributes is the stricter poll projection: also drops eprel_*
|
||||
// keys (those are exposed on item.eprel).
|
||||
func SanitizeV1ProcessAttributes(attrs map[string]any) map[string]any {
|
||||
return sanitizeProductAttributes(attrs, true)
|
||||
}
|
||||
|
||||
func sanitizeProductAttributes(attrs map[string]any, dropEPREL bool) map[string]any {
|
||||
if len(attrs) == 0 {
|
||||
return map[string]any{}
|
||||
}
|
||||
out := make(map[string]any, len(attrs))
|
||||
for k, v := range attrs {
|
||||
key := strings.TrimSpace(SanitizeOutput(k))
|
||||
if key == "" || isReservedProductKey(key) || isInvalidAttributeKey(key) {
|
||||
continue
|
||||
}
|
||||
if dropEPREL && strings.HasPrefix(strings.ToLower(key), "eprel") {
|
||||
continue
|
||||
}
|
||||
s := stringifySpecValue(v)
|
||||
if s == "" || s == "<nil>" {
|
||||
continue
|
||||
}
|
||||
// Prefer canonical dimension/model keys when aliases collide.
|
||||
canon := canonicalizeAttrKey(key)
|
||||
if canon == "" || isReservedProductKey(canon) || isInvalidAttributeKey(canon) {
|
||||
continue
|
||||
}
|
||||
if dropEPREL && strings.HasPrefix(strings.ToLower(canon), "eprel") {
|
||||
continue
|
||||
}
|
||||
if isDimensionKey(canon) && isZeroishString(s) {
|
||||
continue
|
||||
}
|
||||
if _, exists := out[canon]; exists && (canon != key) {
|
||||
continue
|
||||
}
|
||||
out[canon] = SanitizeOutput(s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func canonicalizeAttrKey(k string) string {
|
||||
compact := strings.ToLower(strings.TrimSpace(k))
|
||||
compact = strings.ReplaceAll(compact, "-", "_")
|
||||
compact = strings.ReplaceAll(compact, " ", "_")
|
||||
noUnderscore := strings.ReplaceAll(compact, "_", "")
|
||||
switch noUnderscore {
|
||||
case "netwidth", "width", "sirina":
|
||||
return "width"
|
||||
case "netheight", "height", "visina":
|
||||
return "height"
|
||||
case "netdepth", "depth", "globina":
|
||||
return "depth"
|
||||
case "netmass", "weight", "mass", "teza":
|
||||
return "weight"
|
||||
case "productmodel", "model":
|
||||
return "product_model"
|
||||
case "energijskirazred", "energyclass":
|
||||
return "energy_class"
|
||||
default:
|
||||
return compact
|
||||
}
|
||||
}
|
||||
|
||||
func parseHTMLSpecs(dst map[string]any, s string) {
|
||||
s = capSpecInput(s)
|
||||
matches := htmlLiRe.FindAllStringSubmatch(s, maxSpecPairs)
|
||||
@@ -126,8 +229,9 @@ func parseHTMLSpecs(dst map[string]any, s string) {
|
||||
continue
|
||||
}
|
||||
key, val := splitLabelValue(text)
|
||||
if key != "" && val != "" {
|
||||
dst[attributeKeyFromLabel(key)] = SanitizeOutput(val)
|
||||
attrKey := attributeKeyFromLabel(key)
|
||||
if attrKey != "" && val != "" && !isReservedProductKey(attrKey) && !isInvalidAttributeKey(attrKey) {
|
||||
dst[attrKey] = SanitizeOutput(val)
|
||||
}
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
@@ -198,10 +302,10 @@ func parseCSVLikeSpecs(dst map[string]any, s string) {
|
||||
if len(m) < 3 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(m[1])
|
||||
key := attributeKeyFromLabel(strings.TrimSpace(m[1]))
|
||||
val := strings.TrimSpace(m[2])
|
||||
if key != "" && val != "" {
|
||||
dst[attributeKeyFromLabel(key)] = SanitizeOutput(val)
|
||||
if key != "" && val != "" && !isReservedProductKey(key) && !isInvalidAttributeKey(key) {
|
||||
dst[key] = SanitizeOutput(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user