package eprel import ( "fmt" "strings" ) // ExtractFromAttrs builds the nested EPREL object used by V1 process items and // the dashboard product API (id / label / pdf / energy_class / energy_scale). // Returns nil when no usable EPREL fields are present. // // Nested attrs["eprel"] may use flat export aliases (eprel_id, eprel_label, …); // those are normalized onto the public keys. Flat eprel_* keys fill any gaps. func ExtractFromAttrs(attrs map[string]any) any { if attrs == nil { return nil } out := map[string]any{} if e, ok := attrs["eprel"]; ok && e != nil { switch t := e.(type) { case map[string]any: for k, v := range t { if s := attrString(v); s == "" { continue } else { out[strings.TrimSpace(k)] = s } } default: if s := attrString(e); s != "" { return map[string]any{"id": s} } return nil } } put := func(dstKey string, keys ...string) { if attrString(out[dstKey]) != "" { return } for _, k := range keys { if s := attrString(attrs[k]); s != "" { out[dstKey] = s return } // Also accept aliases already copied from a nested object. if s := attrString(out[k]); s != "" { out[dstKey] = s return } } } put("id", "eprel_id", "EPRELID", "eprelId") put("label", "eprel_label", "eprel_label_url") put("pdf", "eprel_pdf", "eprel_pdf_url") put("energy_class", "eprel_energy_class", "energyClass") put("energy_scale", "eprel_energy_scale", "energyClassRange", "energyScale") return NormalizeShape(out) } // NormalizeShape keeps only the public EPREL keys with non-empty string values. // Alias keys (eprel_id, …) are folded onto id/label/pdf/energy_class/energy_scale. func NormalizeShape(v any) any { if v == nil { return nil } m, ok := v.(map[string]any) if !ok { return nil } if len(m) == 0 { return nil } out := map[string]any{} for k, val := range m { if s := attrString(val); s != "" { out[strings.TrimSpace(k)] = s } } fold := func(dst string, aliases ...string) { if attrString(out[dst]) != "" { for _, a := range aliases { delete(out, a) } return } for _, a := range aliases { if s := attrString(out[a]); s != "" { out[dst] = s break } } for _, a := range aliases { delete(out, a) } } fold("id", "eprel_id", "EPRELID", "eprelId") fold("label", "eprel_label", "eprel_label_url") fold("pdf", "eprel_pdf", "eprel_pdf_url") fold("energy_class", "eprel_energy_class", "energyClass") fold("energy_scale", "eprel_energy_scale", "energyClassRange", "energyScale") canonical := map[string]any{} for _, k := range []string{"id", "label", "pdf", "energy_class", "energy_scale"} { if s := attrString(out[k]); s != "" { canonical[k] = s } } if len(canonical) == 0 { return nil } return canonical } func attrString(v any) string { if v == nil { return "" } switch t := v.(type) { case string: return strings.TrimSpace(t) case float64: // JSON numbers for numeric eprel ids. if t == float64(int64(t)) { return strings.TrimSpace(fmt.Sprintf("%.0f", t)) } return strings.TrimSpace(fmt.Sprint(t)) case int: return fmt.Sprintf("%d", t) case int64: return fmt.Sprintf("%d", t) case map[string]any: for _, k := range []string{"value", "#text", "text", "id"} { if s := attrString(t[k]); s != "" { return s } } return "" default: s := strings.TrimSpace(fmt.Sprint(v)) if s == "" || s == "" { return "" } return s } }