432 lines
14 KiB
C#
432 lines
14 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)
|
|
{
|
|
var lineBuffer = new StringBuilder();
|
|
var hasInlineContent = false;
|
|
|
|
void FlushLineBuffer()
|
|
{
|
|
if (hasInlineContent)
|
|
{
|
|
var text = ApplyAlignment(lineBuffer.ToString(), style);
|
|
commands.Add(CreateCommand(text, style));
|
|
lineBuffer.Clear();
|
|
hasInlineContent = false;
|
|
}
|
|
}
|
|
|
|
foreach (var node in nodes)
|
|
{
|
|
switch (node)
|
|
{
|
|
// Inline nodes - accumulate into line buffer
|
|
case TextNode textNode:
|
|
lineBuffer.Append(textNode.Text);
|
|
hasInlineContent = true;
|
|
break;
|
|
|
|
case BindingNode bindingNode:
|
|
var element = context.Resolve(bindingNode.Path);
|
|
lineBuffer.Append(_formatter.Format(element, bindingNode.Format));
|
|
hasInlineContent = true;
|
|
break;
|
|
|
|
// Styled text - flush buffer, emit styled content, continue accumulating
|
|
case StyledTextNode styledNode:
|
|
FlushLineBuffer();
|
|
InterpretStyledText(styledNode, context, commands, style);
|
|
break;
|
|
|
|
// Block nodes - flush buffer first, then process
|
|
case SeparatorNode separatorNode:
|
|
FlushLineBuffer();
|
|
InterpretSeparator(separatorNode, commands, style);
|
|
break;
|
|
|
|
case EmptyLineNode:
|
|
FlushLineBuffer();
|
|
commands.Add(CreateCommand(string.Empty, style));
|
|
break;
|
|
|
|
case IfNode ifNode:
|
|
FlushLineBuffer();
|
|
InterpretIf(ifNode, context, commands, style);
|
|
break;
|
|
|
|
case ForeachNode foreachNode:
|
|
FlushLineBuffer();
|
|
InterpretForeach(foreachNode, context, commands, style);
|
|
break;
|
|
|
|
case RowNode rowNode:
|
|
FlushLineBuffer();
|
|
InterpretRow(rowNode, context, commands, style);
|
|
break;
|
|
|
|
case ColumnNode columnNode:
|
|
FlushLineBuffer();
|
|
InterpretColumn(columnNode, context, commands, style);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Flush any remaining content
|
|
FlushLineBuffer();
|
|
}
|
|
|
|
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)
|
|
{
|
|
// Apply row-level styles
|
|
var rowStyle = style.Clone();
|
|
ApplyStyles(node.Styles, rowStyle);
|
|
|
|
var lineWidth = rowStyle.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(), rowStyle));
|
|
}
|
|
|
|
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
|
|
};
|
|
}
|
|
}
|