Add constant rich text placeholders
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
2026-08-30 23:23:48 +02:00
parent 28469bf547
commit 527c186090
15 changed files with 529 additions and 15 deletions
@@ -0,0 +1,73 @@
using System.Globalization;
namespace LehrerApp.Templating;
public static class TemplateDataResolver
{
public static IReadOnlyList<string> ValidateConstants(TemplateManifest manifest)
{
var issues = new List<string>();
var definitions = manifest.Placeholders.GroupBy(x => x.Name, StringComparer.Ordinal)
.ToDictionary(x => x.Key, x => x.First(), StringComparer.Ordinal);
foreach (var constant in manifest.Placeholders.Where(x => x.IsConstant))
{
try
{
ConstantValue(constant);
if (constant.Type is not (PlaceholderType.Text or PlaceholderType.Multiline)) continue;
foreach (var reference in TemplateRichText.UsedPlaceholders(constant.ConstantValue ?? ""))
{
if (!definitions.TryGetValue(reference, out var target))
issues.Add($"Konstanter Text „{constant.Name}“ verwendet den nicht deklarierten Platzhalter „{reference}“.");
else if (target.IsConstant)
issues.Add($"Konstanter Text „{constant.Name}“ darf nur externe Platzhalter verwenden; „{reference}“ ist ebenfalls konstant.");
else if (target.Type is not (PlaceholderType.Text or PlaceholderType.Multiline
or PlaceholderType.Date or PlaceholderType.Number))
issues.Add($"Eingebetteter Platzhalter „{reference}“ hat keinen textuell darstellbaren Typ.");
}
}
catch (InvalidDataException ex) { issues.Add(ex.Message); }
}
return issues;
}
public static IReadOnlyDictionary<string, PlaceholderValue> Resolve(TemplateManifest manifest,
IReadOnlyDictionary<string, PlaceholderValue> externalValues)
{
var result = new Dictionary<string, PlaceholderValue>(externalValues, StringComparer.Ordinal);
foreach (var definition in manifest.Placeholders.Where(x => x.IsConstant))
result[definition.Name] = ConstantValue(definition);
return result;
}
public static PlaceholderValue ConstantValue(PlaceholderDefinition definition)
{
var raw = definition.ConstantValue ?? "";
return definition.Type switch
{
PlaceholderType.Text => new TextValue(raw),
PlaceholderType.Multiline => new MultilineValue(raw),
PlaceholderType.Date => new DateValue(ParseDate(definition, raw)),
PlaceholderType.Number => new NumberValue(ParseNumber(definition, raw)),
_ => throw new InvalidDataException(
$"Platzhalter „{definition.Name}“ vom Typ {definition.Type} kann keinen konstanten Textwert verwenden."),
};
}
private static DateOnly ParseDate(PlaceholderDefinition definition, string raw)
{
if (DateOnly.TryParse(raw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)
|| DateOnly.TryParse(raw, CultureInfo.GetCultureInfo("de-DE"), DateTimeStyles.None, out date)) return date;
throw Error(definition, "Datum, z. B. 2026-08-30");
}
private static decimal ParseNumber(PlaceholderDefinition definition, string raw)
{
if (decimal.TryParse(raw, NumberStyles.Number, CultureInfo.InvariantCulture, out var number)
|| decimal.TryParse(raw, NumberStyles.Number, CultureInfo.GetCultureInfo("de-DE"), out number)) return number;
throw Error(definition, "Zahl");
}
private static InvalidDataException Error(PlaceholderDefinition definition, string expected) =>
new($"Konstanter Wert für „{definition.Name}“ ist ungültig; erwartet wird: {expected}.");
}