namespace Core.Printing; /// /// Splits a markdown template that contains exactly one table so a roster can /// share one header and append merged body rows per record. /// public sealed class MarkdownTableStencil { public required string Prefix { get; init; } public required string Header { get; init; } public required string Body { get; init; } public required string Suffix { get; init; } public static bool TryParse(string? template, out MarkdownTableStencil? stencil) { stencil = null; if (string.IsNullOrWhiteSpace(template)) return false; var lines = SplitLines(template); if (!TryFindTable(lines, 0, out var headerIndex, out var bodyStart, out var bodyEnd)) return false; if (bodyStart >= bodyEnd) return false; if (TryFindTable(lines, bodyEnd, out _, out _, out _)) return false; stencil = new MarkdownTableStencil { Prefix = JoinLines(lines[..headerIndex]), Header = JoinLines(lines[headerIndex..bodyStart]), Body = JoinLines(lines[bodyStart..bodyEnd]), Suffix = JoinLines(lines[bodyEnd..]) }; return true; } public string Stitch(string prefix, IEnumerable bodies, string suffix) { ArgumentNullException.ThrowIfNull(bodies); List parts = []; AppendPart(parts, prefix); AppendPart(parts, Header); foreach (var body in bodies) AppendPart(parts, body); AppendPart(parts, suffix); return parts.Count == 0 ? string.Empty : string.Join('\n', parts) + "\n"; } private static void AppendPart(List parts, string? part) { if (string.IsNullOrEmpty(part)) return; var trimmed = part.TrimEnd('\r', '\n'); if (trimmed.Length > 0) parts.Add(trimmed); } private static bool TryFindTable( string[] lines, int start, out int headerIndex, out int bodyStart, out int bodyEnd) { headerIndex = -1; bodyStart = -1; bodyEnd = -1; for (var i = start; i < lines.Length - 1; i++) { if (!IsTableLine(lines[i]) || !IsSeparatorLine(lines[i + 1])) continue; headerIndex = i; bodyStart = i + 2; bodyEnd = bodyStart; while (bodyEnd < lines.Length && IsTableLine(lines[bodyEnd]) && !IsSeparatorLine(lines[bodyEnd])) bodyEnd++; return true; } return false; } private static bool IsTableLine(string line) { var trimmed = line.TrimStart(); return trimmed.StartsWith('|'); } private static bool IsSeparatorLine(string line) => IsTableLine(line) && line.Contains("---", StringComparison.Ordinal); private static string[] SplitLines(string text) => text.Replace("\r\n", "\n", StringComparison.Ordinal).Replace('\r', '\n').Split('\n'); private static string JoinLines(string[] lines) => lines.Length == 0 ? string.Empty : string.Join('\n', lines); }