using System.Globalization;
namespace Campaign_Tracker.Server.LegacyData.Schema;
///
/// Parses the Access schema text dump shipped at
/// Initial Documents/Access_Schema.txt into a strongly-typed
/// for compatibility checking.
///
/// File format (one table per block):
///
/// Table: Contacts
/// ---------------
/// Column: ID Type: 3 Size: Nullable: False
/// Column: EMAIL Type: 130 Size: 255 Nullable: True
///
///
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();
string? currentTable = null;
var currentColumns = new List();
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();
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 sink,
string? tableName,
List 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();
}
}