Skip to content

Commit 1534aca

Browse files
committed
feat: Add Set node to support variable assignment during template rendering
Added the `SetNode` class for defining variables in templates, and implemented the corresponding parsing logic in `TemplateReader`. This allows dynamic variable assignment during template rendering, making variables available to subsequent child nodes, thus improving template flexibility and reusability. Key changes include: - Created the `SetNode` record type - Registered the Set node in the node parsing map - Implemented the `ParseSetNodeAsync` method to parse Set elements - Modified `ParseChildrenAsync` to handle Set nodes and update the scope - Added the `EvaluateSetValueAsync` method for evaluating variable values - Added the `MergeScope` method for merging scope dictionaries
1 parent fa80500 commit 1534aca

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

src/Render/Nodes/SetNode.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Limekuma.Render.Nodes;
2+
3+
public sealed record SetNode(
4+
string Name,
5+
object? Value,
6+
string? Key
7+
) : Node(Key);

src/Render/TemplateReader.NodeParsing.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ await EvaluateExpressionAttributeOrAsync(element, "strokeWidth", 0f, scope),
101101
await EvaluateTemplateAttributeOrAsync(element, "truncateSuffix", string.Empty, scope),
102102
await EvaluateOptionalSmartTemplateAttributeAsync(element, "key", scope));
103103

104+
private async Task<Node> ParseSetNodeAsync(XElement element, object? scope) =>
105+
new SetNode(
106+
GetRequiredAttributeValue(element, "var"),
107+
await EvaluateSetValueAsync(GetRequiredAttributeValue(element, "value"), scope),
108+
await EvaluateOptionalSmartTemplateAttributeAsync(element, "key", scope));
109+
104110
private async Task<Node> ParseIfNodeAsync(XElement element, object? scope)
105111
{
106112
bool pass = await EvaluateRequiredExpressionAsAsync<bool>(GetRequiredAttributeValue(element, "rule"), scope);
@@ -153,21 +159,45 @@ private async Task<string> ResolveIncludePathAsync(XElement element, object? sco
153159
private async Task<List<Node>> ParseChildrenAsync(XElement element, object? scope)
154160
{
155161
List<Node> children = [];
162+
object? currentScope = scope;
156163
foreach (XElement child in element.Elements())
157164
{
158-
children.Add(await ParseElementAsync(child, scope));
165+
Node node = await ParseElementAsync(child, currentScope);
166+
if (node is SetNode setNode)
167+
{
168+
currentScope = MergeScope(currentScope, setNode.Name, setNode.Value);
169+
continue;
170+
}
171+
172+
children.Add(node);
159173
}
160174

161175
return children;
162176
}
163177

178+
private async Task<object?> EvaluateSetValueAsync(string raw, object? scope)
179+
{
180+
if (raw.StartsWith("@{", StringComparison.Ordinal) && raw.EndsWith('}'))
181+
{
182+
return await _expressionEngine.EvalAsync(raw[2..^1], scope);
183+
}
184+
185+
try
186+
{
187+
return await _expressionEngine.EvalAsync(raw, scope);
188+
}
189+
catch
190+
{
191+
return await EvaluateTemplateAsync(raw, scope);
192+
}
193+
}
194+
164195
private async Task<IEnumerable<object>?> EvaluateCollectionAsync(string expression, object? scope)
165196
{
166197
object? rawItems = await _expressionEngine.EvalAsync(expression, scope);
167198
return rawItems switch
168199
{
169200
null => null,
170-
string => null,
171201
IEnumerable<object> objectItems => objectItems,
172202
IEnumerable enumerable => enumerable.Cast<object>(),
173203
IConvertible convertible => Enumerable.Range(0, Math.Max(0, Convert.ToInt32(convertible, CultureInfo.InvariantCulture)))
@@ -191,4 +221,19 @@ private async Task<List<Node>> ParseChildrenAsync(XElement element, object? scop
191221
result[indexName] = index;
192222
return result;
193223
}
224+
225+
private static Dictionary<string, object?> MergeScope(object? parent, string variableName, object? value)
226+
{
227+
Dictionary<string, object?> result = new(StringComparer.OrdinalIgnoreCase);
228+
if (parent is IDictionary<string, object?> map)
229+
{
230+
foreach ((string key, object? mapValue) in map)
231+
{
232+
result[key] = mapValue;
233+
}
234+
}
235+
236+
result[variableName] = value;
237+
return result;
238+
}
194239
}

src/Render/TemplateReader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public TemplateReader(AsyncNCalcEngine expressionEngine)
3030
["Grid"] = ParseGridNodeAsync,
3131
["Image"] = ParseImageNodeAsync,
3232
["Text"] = ParseTextNodeAsync,
33+
["Set"] = ParseSetNodeAsync,
3334
["If"] = ParseIfNodeAsync,
3435
["For"] = ParseForNodeAsync,
3536
["Include"] = ParseIncludeNodeAsync

0 commit comments

Comments
 (0)