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 Interpret(TemplateNode template, string jsonData) { var document = JsonDocument.Parse(jsonData); var context = new DataContext(document.RootElement); return Interpret(template, context); } public List Interpret(TemplateNode template, DataContext context) { var commands = new List(); InterpretNodes(template.ChildNodes, context, commands, new StyleContext()); return commands; } private void InterpretNodes( IReadOnlyList nodes, DataContext context, List commands, StyleContext style) { foreach (var node in nodes) { InterpretNode(node, context, commands, style); } } private void InterpretNode( ITemplateNode node, DataContext context, List 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 commands, StyleContext style) { var text = ApplyAlignment(node.Text, style); commands.Add(CreateCommand(text, style)); } private void InterpretBinding( BindingNode node, DataContext context, List 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 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 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 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 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 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 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 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 }; } }