using System.Text.Json.Nodes; using System.Text.RegularExpressions; using StellaOps.Notify.Models; namespace StellaOps.Notifier.Worker.Processing; /// /// Simple Handlebars-like template renderer supporting {{property}} and {{#each}} blocks. /// public sealed partial class SimpleTemplateRenderer : INotifyTemplateRenderer { private static readonly Regex PlaceholderPattern = PlaceholderRegex(); private static readonly Regex EachBlockPattern = EachBlockRegex(); public string Render(NotifyTemplate template, JsonNode? payload) { ArgumentNullException.ThrowIfNull(template); var body = template.Body; if (string.IsNullOrWhiteSpace(body)) { return string.Empty; } // Process {{#each}} blocks first body = ProcessEachBlocks(body, payload); // Then substitute simple placeholders body = SubstitutePlaceholders(body, payload); return body; } private static string ProcessEachBlocks(string body, JsonNode? payload) { return EachBlockPattern.Replace(body, match => { var collectionPath = match.Groups[1].Value.Trim(); var innerTemplate = match.Groups[2].Value; var collection = ResolvePath(payload, collectionPath); if (collection is not JsonObject obj) { return string.Empty; } var results = new List(); foreach (var (key, value) in obj) { var itemResult = innerTemplate .Replace("{{@key}}", key) .Replace("{{this}}", value?.ToString() ?? string.Empty); results.Add(itemResult); } return string.Join(string.Empty, results); }); } private static string SubstitutePlaceholders(string body, JsonNode? payload) { return PlaceholderPattern.Replace(body, match => { var path = match.Groups[1].Value.Trim(); var resolved = ResolvePath(payload, path); return resolved?.ToString() ?? string.Empty; }); } private static JsonNode? ResolvePath(JsonNode? root, string path) { if (root is null || string.IsNullOrWhiteSpace(path)) { return null; } var segments = path.Split('.'); var current = root; foreach (var segment in segments) { if (current is JsonObject obj && obj.TryGetPropertyValue(segment, out var next)) { current = next; } else { return null; } } return current; } [GeneratedRegex(@"\{\{([^#/}]+)\}\}", RegexOptions.Compiled)] private static partial Regex PlaceholderRegex(); [GeneratedRegex(@"\{\{#each\s+([^}]+)\}\}(.*?)\{\{/each\}\}", RegexOptions.Compiled | RegexOptions.Singleline)] private static partial Regex EachBlockRegex(); }