Interview notes can print each student's ranked events and a shared attribute legend from the same catalog as the ranking index, without leftover table markup or collapsed answer space. Co-authored-by: Cursor <cursoragent@cursor.com>
77 lines
2.7 KiB
C#
77 lines
2.7 KiB
C#
namespace Core.Printing;
|
|
|
|
/// <summary>
|
|
/// Builds a case-insensitive token map. Fill order is imported, then entity, then chapter
|
|
/// so built-in names win over an imported column with the same name.
|
|
/// </summary>
|
|
public static class PrintTokenMap
|
|
{
|
|
public static Dictionary<string, string> Create() =>
|
|
new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public static Dictionary<string, string> Build(
|
|
IReadOnlyDictionary<string, string?>? imported,
|
|
IReadOnlyDictionary<string, string?>? entity,
|
|
IReadOnlyDictionary<string, string?>? chapter)
|
|
{
|
|
var map = Create();
|
|
Apply(map, imported);
|
|
Apply(map, entity);
|
|
Apply(map, chapter);
|
|
return map;
|
|
}
|
|
|
|
public static void Apply(IDictionary<string, string> map, IReadOnlyDictionary<string, string?>? values)
|
|
{
|
|
if (values is null)
|
|
return;
|
|
|
|
foreach (var (key, value) in values)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
continue;
|
|
map[key] = Escape(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Treats substituted values as plain text so they cannot change markdown or inject HTML.
|
|
/// Line breaks are flattened so a value cannot end a markdown table row.
|
|
/// </summary>
|
|
public static string Escape(string? value)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
return string.Empty;
|
|
|
|
return FlattenLines(value)
|
|
.Replace("\\", "\\\\", StringComparison.Ordinal)
|
|
.Replace("|", "\\|", StringComparison.Ordinal)
|
|
.Replace("*", "\\*", StringComparison.Ordinal)
|
|
.Replace("_", "\\_", StringComparison.Ordinal)
|
|
.Replace("`", "\\`", StringComparison.Ordinal)
|
|
.Replace("[", "\\[", StringComparison.Ordinal)
|
|
.Replace("]", "\\]", StringComparison.Ordinal)
|
|
.Replace("<", "<", StringComparison.Ordinal)
|
|
.Replace(">", ">", StringComparison.Ordinal);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Escapes a value for insertion into generated print HTML (badge labels).
|
|
/// </summary>
|
|
public static string EscapeHtml(string? value)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
return string.Empty;
|
|
|
|
return FlattenLines(value)
|
|
.Replace("&", "&", StringComparison.Ordinal)
|
|
.Replace("<", "<", StringComparison.Ordinal)
|
|
.Replace(">", ">", StringComparison.Ordinal)
|
|
.Replace("\"", """, StringComparison.Ordinal);
|
|
}
|
|
|
|
private static string FlattenLines(string value) =>
|
|
string.Join(' ',
|
|
value.Split(['\r', '\n', '\u2028', '\u2029'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
|
}
|