feat: stitch page-printer tables into one roster and add chapter team-count tokens
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>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user