When New page per record is off, a single-table template shares one header across records. Event and team pages can print regional/state team counts and nationals eligibility. Co-authored-by: Cursor <cursoragent@cursor.com>
109 lines
3.1 KiB
C#
109 lines
3.1 KiB
C#
namespace Core.Printing;
|
|
|
|
/// <summary>
|
|
/// Splits a markdown template that contains exactly one table so a roster can
|
|
/// share one header and append merged body rows per record.
|
|
/// </summary>
|
|
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<string> bodies, string suffix)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(bodies);
|
|
|
|
List<string> 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<string> 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);
|
|
}
|