using System.Text; namespace RioJoy.Core.Overlay; /// /// Imports the legacy "Goobie" label data file — the Google-Sheet export consumed /// by sg-goobie-MFD.scm (see docs/reference/customBackground/TEST.data). The format /// is Lisp-ish: ; starts a comment to end of line, a parenthesized list of /// bare field names (matching the template layer/region names) comes first, then /// one parenthesized list of quoted strings per output image (row). Field 0 is the /// row's file tag (output name); the rest are the per-region label text. /// /// Each row maps directly to an label set /// (region name → text), so this is the bridge from the old sheet to a profile's /// . /// public static class GoobieDataImporter { /// A parsed data file: the field names and one label map per row. public sealed record Sheet( IList Fields, IList> Rows); public static Sheet Load(string path) => Parse(File.ReadAllText(path)); /// Parse the data file text into fields + rows. public static Sheet Parse(string text) { if (text is null) throw new ArgumentNullException(nameof(text)); List> lists = ReadLists(text); if (lists.Count == 0) throw new FormatException("No lists found; expected a header list of field names."); List fields = lists[0]; var rows = new List>(); for (int i = 1; i < lists.Count; i++) { List values = lists[i]; var map = new Dictionary(StringComparer.OrdinalIgnoreCase); for (int f = 0; f < fields.Count && f < values.Count; f++) map[fields[f]] = values[f]; rows.Add(map); } return new Sheet(fields, rows); } /// /// Convenience: the label map for a single row (default the first), with empty /// values dropped — ready to hand to . /// public static IDictionary LabelsForRow(Sheet sheet, int rowIndex = 0) { if (sheet is null) throw new ArgumentNullException(nameof(sheet)); if (rowIndex < 0 || rowIndex >= sheet.Rows.Count) throw new ArgumentOutOfRangeException(nameof(rowIndex)); return sheet.Rows[rowIndex] .Where(kv => !string.IsNullOrEmpty(kv.Value)) .ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase); } // Tokenize into parenthesized lists. Barewords and "quoted strings" are both // returned as their text (quotes stripped); ';' comments run to end of line. private static List> ReadLists(string s) { var lists = new List>(); List? current = null; int i = 0; while (i < s.Length) { char c = s[i]; if (c == ';') { while (i < s.Length && s[i] != '\n') i++; } else if (char.IsWhiteSpace(c)) { i++; } else if (c == '(') { current = new List(); i++; } else if (c == ')') { if (current is not null) { lists.Add(current); current = null; } i++; } else if (c == '"') { i++; var sb = new StringBuilder(); while (i < s.Length && s[i] != '"') { if (s[i] == '\\' && i + 1 < s.Length) i++; // unescape \" and \\ sb.Append(s[i++]); } i++; // closing quote current?.Add(sb.ToString()); } else { int start = i; while (i < s.Length && !char.IsWhiteSpace(s[i]) && s[i] is not ('(' or ')' or '"' or ';')) i++; current?.Add(s[start..i]); } } return lists; } }