templates support
This commit is contained in:
428
Inspectron.Epson.Templates/Interpreter/TemplateInterpreter.cs
Normal file
428
Inspectron.Epson.Templates/Interpreter/TemplateInterpreter.cs
Normal file
@@ -0,0 +1,428 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user