Files
Print_server/Inspectron.Epson.Templates/Interpreter/TemplateInterpreter.cs
2026-01-21 15:56:37 +01:00

429 lines
13 KiB
C#

using System.Globalization;
using System.Text;
using System.Text.Json;
using Inspectron.Epson.PrintServer.Printers.Utils;
using Inspectron.Epson.Templates.Configuration;
using Inspectron.Epson.Templates.Language;
using Inspectron.Epson.Templates.Language.Nodes;
namespace Inspectron.Epson.Templates.Interpreter;
public class TemplateInterpreter
{
private readonly PrinterProfile _profile;
private readonly ExpressionEvaluator _evaluator;
private readonly FormatResolver _formatter;
public TemplateInterpreter(PrinterProfile profile, CultureInfo? culture = null)
{
_profile = profile ?? throw new ArgumentNullException(nameof(profile));
_evaluator = new ExpressionEvaluator();
_formatter = new FormatResolver(culture);
}
public List<PrintCommand> Interpret(TemplateNode template, string jsonData)
{
var document = JsonDocument.Parse(jsonData);
var context = new DataContext(document.RootElement);
return Interpret(template, context);
}
public List<PrintCommand> Interpret(TemplateNode template, DataContext context)
{
var commands = new List<PrintCommand>();
InterpretNodes(template.ChildNodes, context, commands, new StyleContext());
return commands;
}
private void InterpretNodes(
IReadOnlyList<ITemplateNode> nodes,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
foreach (var node in nodes)
{
InterpretNode(node, context, commands, style);
}
}
private void InterpretNode(
ITemplateNode node,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
switch (node)
{
case TextNode textNode:
InterpretText(textNode, context, commands, style);
break;
case BindingNode bindingNode:
InterpretBinding(bindingNode, context, commands, style);
break;
case StyledTextNode styledNode:
InterpretStyledText(styledNode, context, commands, style);
break;
case SeparatorNode separatorNode:
InterpretSeparator(separatorNode, commands, style);
break;
case EmptyLineNode:
commands.Add(CreateCommand(string.Empty, style));
break;
case IfNode ifNode:
InterpretIf(ifNode, context, commands, style);
break;
case ForeachNode foreachNode:
InterpretForeach(foreachNode, context, commands, style);
break;
case RowNode rowNode:
InterpretRow(rowNode, context, commands, style);
break;
case ColumnNode columnNode:
InterpretColumn(columnNode, context, commands, style);
break;
}
}
private void InterpretText(
TextNode node,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
var text = ApplyAlignment(node.Text, style);
commands.Add(CreateCommand(text, style));
}
private void InterpretBinding(
BindingNode node,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
var element = context.Resolve(node.Path);
var text = _formatter.Format(element, node.Format);
text = ApplyAlignment(text, style);
commands.Add(CreateCommand(text, style));
}
private void InterpretStyledText(
StyledTextNode node,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
// Create new style context with inherited and new styles
var newStyle = style.Clone();
ApplyStyles(node.Styles, newStyle);
// Build content string from child nodes
var contentBuilder = new StringBuilder();
foreach (var child in node.ContentNodes)
{
switch (child)
{
case TextNode textNode:
contentBuilder.Append(textNode.Text);
break;
case BindingNode bindingNode:
var element = context.Resolve(bindingNode.Path);
contentBuilder.Append(_formatter.Format(element, bindingNode.Format));
break;
}
}
var text = contentBuilder.ToString();
text = ApplyAlignment(text, newStyle);
commands.Add(CreateCommand(text, newStyle));
}
private void InterpretSeparator(
SeparatorNode node,
List<PrintCommand> commands,
StyleContext style)
{
var width = style.IsBig ? _profile.BigLineWidth : _profile.LineWidth;
var ch = node.Style switch
{
SeparatorStyle.Dash => '-',
SeparatorStyle.Equals => '=',
SeparatorStyle.Star => '*',
SeparatorStyle.Tilde => '~',
_ => '-'
};
var line = new string(ch, width);
commands.Add(CreateCommand(line, style));
}
private void InterpretIf(
IfNode node,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
// Evaluate @if condition
if (_evaluator.Evaluate(node.IfBranch.Condition, context))
{
InterpretNodes(node.IfBranch.Body, context, commands, style);
return;
}
// Evaluate @elseif conditions
foreach (var branch in node.ElseIfBranches)
{
if (_evaluator.Evaluate(branch.Condition, context))
{
InterpretNodes(branch.Body, context, commands, style);
return;
}
}
// Execute @else branch if present
if (node.ElseBranch != null)
{
InterpretNodes(node.ElseBranch, context, commands, style);
}
}
private void InterpretForeach(
ForeachNode node,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
var collection = context.ResolveCollection(node.CollectionPath).ToList();
var count = collection.Count;
for (int i = 0; i < count; i++)
{
var item = collection[i];
var childContext = context.CreateChildContext(node.ItemVariable, item, i, count);
InterpretNodes(node.Body, childContext, commands, style);
}
}
private void InterpretRow(
RowNode node,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
var lineWidth = style.IsBig ? _profile.BigLineWidth : _profile.LineWidth;
var rowBuilder = new StringBuilder();
foreach (var column in node.Columns)
{
// Build column content
var columnContent = new StringBuilder();
foreach (var child in column.ContentNodes)
{
switch (child)
{
case TextNode textNode:
columnContent.Append(textNode.Text);
break;
case BindingNode bindingNode:
var element = context.Resolve(bindingNode.Path);
columnContent.Append(_formatter.Format(element, bindingNode.Format));
break;
case StyledTextNode styledNode:
foreach (var styledChild in styledNode.ContentNodes)
{
if (styledChild is TextNode st)
columnContent.Append(st.Text);
else if (styledChild is BindingNode sb)
{
var elem = context.Resolve(sb.Path);
columnContent.Append(_formatter.Format(elem, sb.Format));
}
}
break;
}
}
var content = columnContent.ToString();
var width = column.Width > 0 ? column.Width : content.Length;
// Apply column alignment
var aligned = column.Alignment switch
{
ColumnAlignment.Right => content.PadLeft(width),
ColumnAlignment.Center => CenterText(content, width),
_ => content.PadRight(width)
};
// Truncate if too long
if (aligned.Length > width && width > 0)
{
aligned = aligned[..width];
}
rowBuilder.Append(aligned);
}
commands.Add(CreateCommand(rowBuilder.ToString(), style));
}
private void InterpretColumn(
ColumnNode node,
DataContext context,
List<PrintCommand> commands,
StyleContext style)
{
// Standalone column - just output content with width constraints
var contentBuilder = new StringBuilder();
foreach (var child in node.ContentNodes)
{
switch (child)
{
case TextNode textNode:
contentBuilder.Append(textNode.Text);
break;
case BindingNode bindingNode:
var element = context.Resolve(bindingNode.Path);
contentBuilder.Append(_formatter.Format(element, bindingNode.Format));
break;
}
}
var content = contentBuilder.ToString();
var width = node.Width > 0 ? node.Width : content.Length;
var aligned = node.Alignment switch
{
ColumnAlignment.Right => content.PadLeft(width),
ColumnAlignment.Center => CenterText(content, width),
_ => content.PadRight(width)
};
if (aligned.Length > width && width > 0)
{
aligned = aligned[..width];
}
commands.Add(CreateCommand(aligned, style));
}
private void ApplyStyles(IReadOnlyList<string> styles, StyleContext styleContext)
{
foreach (var style in styles)
{
var lower = style.ToLowerInvariant();
if (lower.StartsWith("spacing:"))
{
if (int.TryParse(lower[8..], out var spacing))
{
styleContext.LineSpacing = spacing;
}
continue;
}
switch (lower)
{
case "bold":
styleContext.IsBold = true;
break;
case "big":
styleContext.IsBig = true;
break;
case "tall":
styleContext.IsTall = true;
break;
case "red":
styleContext.IsRed = true;
break;
case "center":
styleContext.Alignment = TextAlignment.Center;
break;
case "right":
styleContext.Alignment = TextAlignment.Right;
break;
case "left":
styleContext.Alignment = TextAlignment.Left;
break;
}
}
}
private string ApplyAlignment(string text, StyleContext style)
{
if (style.Alignment == TextAlignment.Left)
{
return text;
}
var width = style.IsBig ? _profile.BigLineWidth : _profile.LineWidth;
return style.Alignment switch
{
TextAlignment.Center => CenterText(text, width),
TextAlignment.Right => text.PadLeft(width),
_ => text
};
}
private string CenterText(string text, int width)
{
if (string.IsNullOrEmpty(text) || text.Length >= width)
{
return text;
}
var totalPadding = width - text.Length;
var leftPadding = totalPadding / 2;
var rightPadding = totalPadding - leftPadding;
return new string(' ', leftPadding) + text + new string(' ', rightPadding);
}
private PrintCommand CreateCommand(string text, StyleContext style)
{
var command = new PrintCommand(text, style.IsBig, style.IsBold)
{
IsTall = style.IsTall,
IsRed = style.IsRed && _profile.SupportsRed,
SetLineSpacing = style.LineSpacing
};
return command;
}
}
internal enum TextAlignment
{
Left,
Center,
Right
}
internal class StyleContext
{
public bool IsBold { get; set; }
public bool IsBig { get; set; }
public bool IsTall { get; set; }
public bool IsRed { get; set; }
public TextAlignment Alignment { get; set; } = TextAlignment.Left;
public int? LineSpacing { get; set; }
public StyleContext Clone()
{
return new StyleContext
{
IsBold = IsBold,
IsBig = IsBig,
IsTall = IsTall,
IsRed = IsRed,
Alignment = Alignment,
LineSpacing = LineSpacing
};
}
}