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);
}