Files
meeting-assistant/MeetingAssistant/Workflow/MeetingWorkflowTemplateRenderer.cs
codex b774ccc375
PR and Push Build/Test / build-and-test (push) Successful in 18m41s
Add resilient Azure offline transcription backlog
2026-06-15 17:26:34 +02:00

79 lines
2.2 KiB
C#

using System.Text.RegularExpressions;
using System.Net;
using RazorLight;
namespace MeetingAssistant.Workflow;
internal sealed class MeetingWorkflowTemplateRenderer
{
private static readonly Regex EmailAddressPattern = new(
@"(?<![A-Za-z0-9.!#$%&'*+/=?^_`{|}~-])(?<local>[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+)@(?<domain>(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63})(?![A-Za-z0-9-])",
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
private readonly RazorLightEngine razorEngine = MeetingWorkflowRazorEngineFactory.Create();
public async Task<string> RenderAsync(
string template,
MeetingWorkflowTemplateModel model)
{
if (!ContainsRazorTemplateSyntax(template))
{
return template;
}
var rendered = await razorEngine.CompileRenderStringAsync(
Guid.NewGuid().ToString("N"),
EscapeEmailAddressAtSigns(template),
model);
return WebUtility.HtmlDecode(rendered);
}
public static bool ContainsRazorTemplateSyntax(string value)
{
if (!value.Contains('@', StringComparison.Ordinal))
{
return false;
}
var emailAtSigns = EmailAddressPattern
.Matches(value)
.Select(match => match.Index + match.Value.IndexOf('@', StringComparison.Ordinal))
.ToHashSet();
for (var index = 0; index < value.Length; index++)
{
if (value[index] != '@')
{
continue;
}
if (index + 1 < value.Length && value[index + 1] == '@')
{
index++;
continue;
}
if (emailAtSigns.Contains(index))
{
continue;
}
return true;
}
return false;
}
private static string EscapeEmailAddressAtSigns(string value)
{
if (!value.Contains('@', StringComparison.Ordinal))
{
return value;
}
return EmailAddressPattern.Replace(
value,
match => match.Value.Replace("@", "@@", StringComparison.Ordinal));
}
}