|
- using System.Globalization;
-
- namespace Campaign_Tracker.Server.LegacyData.Schema;
-
- /// <summary>
- /// Parses the Access schema text dump shipped at
- /// <c>Initial Documents/Access_Schema.txt</c> into a strongly-typed
- /// <see cref="LegacySchemaBaseline"/> for compatibility checking.
- ///
- /// File format (one table per block):
- /// <code>
- /// Table: Contacts
- /// ---------------
- /// Column: ID Type: 3 Size: Nullable: False
- /// Column: EMAIL Type: 130 Size: 255 Nullable: True
- /// </code>
- /// </summary>
- public static class LegacySchemaBaselineParser
- {
- public static LegacySchemaBaseline ParseFile(string filePath, DateTimeOffset capturedAt)
- {
- if (!File.Exists(filePath))
- {
- throw new FileNotFoundException(
- $"Legacy schema baseline file not found: {filePath}", filePath);
- }
-
- var text = File.ReadAllText(filePath);
- return Parse(text, filePath, capturedAt);
- }
-
- public static LegacySchemaBaseline Parse(string text, string source, DateTimeOffset capturedAt)
- {
- var tables = new List<LegacyTableDefinition>();
- string? currentTable = null;
- var currentColumns = new List<LegacyColumnDefinition>();
-
- foreach (var rawLine in text.Split('\n'))
- {
- var line = rawLine.TrimEnd('\r').TrimEnd();
- if (line.Length == 0) continue;
- if (line.All(c => c == '-')) continue;
-
- if (line.StartsWith("Table:", StringComparison.Ordinal))
- {
- FlushTable(tables, currentTable, currentColumns);
- currentTable = line["Table:".Length..].Trim();
- currentColumns = new List<LegacyColumnDefinition>();
- continue;
- }
-
- var trimmed = line.TrimStart();
- if (!trimmed.StartsWith("Column:", StringComparison.Ordinal)) continue;
-
- currentColumns.Add(ParseColumn(trimmed));
- }
-
- FlushTable(tables, currentTable, currentColumns);
- return new LegacySchemaBaseline(tables, source, capturedAt);
- }
-
- private static void FlushTable(
- List<LegacyTableDefinition> sink,
- string? tableName,
- List<LegacyColumnDefinition> columns)
- {
- if (string.IsNullOrWhiteSpace(tableName) || columns.Count == 0) return;
- sink.Add(new LegacyTableDefinition(tableName, columns.ToArray()));
- }
-
- private static LegacyColumnDefinition ParseColumn(string line)
- {
- // "Column: NAME Type: 130 Size: 255 Nullable: True"
- var name = ReadField(line, "Column:", ["Type:"]);
- var typeRaw = ReadField(line, "Type:", ["Size:", "Nullable:"]);
- var sizeRaw = ReadField(line, "Size:", ["Nullable:"]);
- var nullableRaw = ReadField(line, "Nullable:", []);
-
- if (!int.TryParse(typeRaw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var typeCode))
- {
- throw new FormatException($"Invalid Type code in column line: {line}");
- }
-
- int? size = null;
- if (!string.IsNullOrWhiteSpace(sizeRaw) &&
- int.TryParse(sizeRaw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedSize))
- {
- size = parsedSize;
- }
-
- var nullable = string.Equals(nullableRaw, "True", StringComparison.OrdinalIgnoreCase);
- return new LegacyColumnDefinition(name, typeCode, size, nullable);
- }
-
- private static string ReadField(string line, string label, string[] terminators)
- {
- var start = line.IndexOf(label, StringComparison.Ordinal);
- if (start < 0) return string.Empty;
- start += label.Length;
-
- var end = line.Length;
- foreach (var terminator in terminators)
- {
- var t = line.IndexOf(terminator, start, StringComparison.Ordinal);
- if (t >= 0 && t < end) end = t;
- }
-
- return line[start..end].Trim();
- }
- }
|