namespace Core.Printing; /// /// 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. /// public static class PrintTokenMap { public static Dictionary Create() => new(StringComparer.OrdinalIgnoreCase); public static Dictionary Build( IReadOnlyDictionary? imported, IReadOnlyDictionary? entity, IReadOnlyDictionary? chapter) { var map = Create(); Apply(map, imported); Apply(map, entity); Apply(map, chapter); return map; } public static void Apply(IDictionary map, IReadOnlyDictionary? values) { if (values is null) return; foreach (var (key, value) in values) { if (string.IsNullOrWhiteSpace(key)) continue; map[key] = Escape(value); } } /// /// Treats substituted values as plain text so they cannot change markdown or inject HTML. /// public static string Escape(string? value) { if (string.IsNullOrEmpty(value)) return string.Empty; return 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); } }