using System.Text.RegularExpressions;
namespace Core.Printing;
///
/// Replaces {{Token}} placeholders from a case-insensitive map.
/// Unknown tokens are left unchanged. Known empty values become blank.
/// {{PageBreak}} becomes a print page break after HTML conversion.
/// {{AnswerSpace}} becomes ruled write-in space after HTML conversion.
///
public static class NoteTemplateMerger
{
public const string PageBreakToken = "PageBreak";
public const string PageBreakSentinel = "";
public const string PageBreakHtml = "
";
public const string AnswerSpaceToken = "AnswerSpace";
public const string AnswerSpaceSentinel = "";
public const string AnswerSpaceHtml = "";
private readonly record struct LayoutToken(string Name, string Sentinel, string Html);
private static readonly LayoutToken[] LayoutTokens =
[
new(PageBreakToken, PageBreakSentinel, PageBreakHtml),
new(AnswerSpaceToken, AnswerSpaceSentinel, AnswerSpaceHtml)
];
private static readonly Regex TokenRegex = new(@"\{\{([^}]+)\}\}", RegexOptions.Compiled);
public static string Merge(string? template, IReadOnlyDictionary tokens)
{
if (string.IsNullOrEmpty(template))
return string.Empty;
ArgumentNullException.ThrowIfNull(tokens);
return TokenRegex.Replace(template, match =>
{
var key = match.Groups[1].Value.Trim();
if (key.Length == 0)
return match.Value;
foreach (var layout in LayoutTokens)
{
if (key.Equals(layout.Name, StringComparison.OrdinalIgnoreCase))
return layout.Sentinel;
}
return tokens.TryGetValue(key, out var value)
? value ?? string.Empty
: match.Value;
});
}
///
/// Turns layout sentinels into HTML after markdown has been rendered.
///
public static string ApplyLayout(string? html)
{
if (string.IsNullOrEmpty(html))
return string.Empty;
foreach (var layout in LayoutTokens)
{
html = html
.Replace($"{layout.Sentinel}
", layout.Html, StringComparison.Ordinal)
.Replace(layout.Sentinel, layout.Html, StringComparison.Ordinal);
}
return html;
}
}