package topdata import ( "fmt" "strings" ) func isNullLike(value any) bool { if value == nil { return true } text, ok := value.(string) if !ok { return false } return strings.TrimSpace(text) == nullValue } func normalizeWikiMetadataKey(key string) string { switch normalizeMetadataKey(key) { case "reqfeats", "req_feats": return "reqfeats" case "generate", "wikigenerate": return "generate" case "canonical", "wikicanonical": return "canonical" case "status", "wikistatus": return "status" case "progressionnotes", "progression_notes", "classprogressionnotes", "class_progression_notes": return "progression_notes" default: return "" } } func legacyWikiMetadataField(key string) string { switch normalizeMetadataKey(key) { case "wikireqfeats": return "reqfeats" case "wikigenerate": return "generate" case "wikicanonical": return "canonical" case "wikistatus": return "status" default: return "" } } func parseWikiMetadata(raw any) (map[string]any, error) { obj, ok := raw.(map[string]any) if !ok { return nil, fmt.Errorf("must be an object") } wiki := map[string]any{} for key, value := range obj { normalized := normalizeWikiMetadataKey(key) if normalized == "" { return nil, fmt.Errorf("unknown field %q", key) } if isNullLike(value) { continue } wiki[normalized] = cloneAuthoringValue(value) } return wiki, nil } func mergeMetadataMaps(base, overlay map[string]any) map[string]any { if len(base) == 0 && len(overlay) == 0 { return nil } out := map[string]any{} for key, value := range base { out[key] = cloneAuthoringValue(value) } for key, value := range overlay { baseValue, hasBase := out[key] baseMap, baseIsMap := baseValue.(map[string]any) valueMap, valueIsMap := value.(map[string]any) if hasBase && baseIsMap && valueIsMap { out[key] = mergeMetadataMaps(baseMap, valueMap) continue } out[key] = cloneAuthoringValue(value) } if len(out) == 0 { return nil } return out } func parseExistingMetadata(row map[string]any) (map[string]any, error) { rawMeta, ok := lookupField(row, "meta") if !ok { return nil, nil } return parseRowMetadata(rawMeta) } func setLegacyWikiMetadata(row map[string]any, field string, value any) error { wikiKey := legacyWikiMetadataField(field) if wikiKey == "" { return nil } meta, err := parseExistingMetadata(row) if err != nil { return err } if meta == nil { meta = map[string]any{} } wiki, _ := meta["wiki"].(map[string]any) if wiki == nil { wiki = map[string]any{} } if isNullLike(value) { delete(wiki, wikiKey) } else { wiki[wikiKey] = cloneAuthoringValue(value) } if len(wiki) == 0 { delete(meta, "wiki") } else { meta["wiki"] = wiki } if len(meta) == 0 { delete(row, "meta") } else { row["meta"] = meta } return nil } func canonicalizeWikiMetadataDocument(obj map[string]any) { if rawColumns, ok := obj["columns"].([]any); ok { filtered := make([]any, 0, len(rawColumns)) for _, raw := range rawColumns { column, ok := raw.(string) if ok && isAuthoringOnlyField(column) { continue } filtered = append(filtered, raw) } obj["columns"] = filtered } if rows, ok := obj["rows"].([]any); ok { for _, raw := range rows { row, ok := raw.(map[string]any) if !ok { continue } canonicalizeWikiMetadataRow(row) } } if entries, ok := obj["entries"].(map[string]any); ok { for _, raw := range entries { row, ok := raw.(map[string]any) if !ok { continue } canonicalizeWikiMetadataRow(row) } } if overrides, ok := obj["overrides"].([]any); ok { for _, raw := range overrides { row, ok := raw.(map[string]any) if !ok { continue } canonicalizeWikiMetadataRow(row) } } } func canonicalizeWikiMetadataRow(row map[string]any) { for _, field := range []string{"WIKIREQFEATS", "WIKIGENERATE", "WIKICANONICAL", "WIKISTATUS"} { value, ok := row[field] if !ok { continue } _ = setLegacyWikiMetadata(row, field, value) delete(row, field) } }