Files
Print_server/Inspectron.Epson.TemplateEngine/Parsing/TemplateParser.cs
2026-02-03 16:40:25 +01:00

272 lines
8.3 KiB
C#

using System.Globalization;
using System.Xml.Linq;
namespace Inspectron.Epson.TemplateEngine.Parsing;
public class TemplateParser
{
public ReceiptNode Parse(string xml)
{
XDocument doc;
try
{
doc = XDocument.Parse(xml);
}
catch (Exception ex)
{
throw new TemplateParsingException($"Invalid XML: {ex.Message}", ex);
}
var root = doc.Root;
if (root == null || root.Name.LocalName != "receipt")
throw new TemplateParsingException("Root element must be <receipt>");
var receiptNode = new ReceiptNode();
ParseChildren(root, receiptNode.Children);
return receiptNode;
}
private void ParseChildren(XElement parent, List<TemplateNode> children)
{
foreach (var element in parent.Elements())
{
var node = ParseElement(element);
if (node != null)
children.Add(node);
}
}
private TemplateNode? ParseElement(XElement element)
{
return element.Name.LocalName switch
{
"line" => ParseLine(element),
"columns" => ParseColumns(element),
"row" => ParseRow(element),
"separator" => ParseSeparator(element),
"cut" => new CutNode(),
"feed" => ParseFeed(element),
"foreach" => ParseForeach(element),
"if" => ParseIf(element),
"else" => ParseElse(element),
"table" => ParseTable(element),
_ => throw new TemplateParsingException($"Unknown element: <{element.Name.LocalName}>")
};
}
private LineNode ParseLine(XElement element)
{
var node = new LineNode();
ApplyFormatting(element, node);
// Text content: either inner text (with {{}} expressions) or empty line
var text = GetTextContent(element);
node.Text = text;
node.Align = GetAttr(element, "align", "left");
node.Wrap = GetBoolAttr(element, "wrap");
node.WrapIndent = GetIntAttr(element, "wrapIndent", 0);
return node;
}
private ColumnsNode ParseColumns(XElement element)
{
var node = new ColumnsNode();
ApplyFormatting(element, node);
node.Left = GetAttr(element, "left", "");
node.Right = GetAttr(element, "right", "");
node.Wrap = GetBoolAttr(element, "wrap");
node.WrapIndent = GetIntAttr(element, "wrapIndent", 0);
return node;
}
private RowNode ParseRow(XElement element)
{
var node = new RowNode();
ApplyFormatting(element, node);
foreach (var colElement in element.Elements("col"))
{
var col = new ColumnDef
{
Text = GetTextContent(colElement),
Align = GetAttr(colElement, "align", "left"),
Wrap = GetBoolAttr(colElement, "wrap")
};
ParseColumnWidth(colElement, col);
node.Columns.Add(col);
}
return node;
}
private SeparatorNode ParseSeparator(XElement element)
{
var charAttr = GetAttr(element, "char", "-");
return new SeparatorNode
{
Character = string.IsNullOrEmpty(charAttr) ? '-' : charAttr[0]
};
}
private FeedNode ParseFeed(XElement element)
{
return new FeedNode
{
Lines = GetIntAttr(element, "lines", 1)
};
}
private ForeachNode ParseForeach(XElement element)
{
var items = GetRequiredAttr(element, "items", "foreach")!;
var var_ = GetRequiredAttr(element, "var", "foreach")!;
var node = new ForeachNode { Items = items, Var = var_ };
ParseChildren(element, node.Children);
return node;
}
private IfNode ParseIf(XElement element)
{
var test = GetRequiredAttr(element, "test", "if")!;
var node = new IfNode { Test = test };
ParseChildren(element, node.Children);
return node;
}
private ElseNode ParseElse(XElement element)
{
var node = new ElseNode();
ParseChildren(element, node.Children);
return node;
}
private TableNode ParseTable(XElement element)
{
var node = new TableNode
{
Items = GetAttr(element, "items", null),
Var = GetAttr(element, "var", null),
HeaderItems = GetAttr(element, "headerItems", null)
};
foreach (var colElement in element.Elements("col"))
{
var col = new ColumnDef
{
Text = GetTextContent(colElement),
Align = GetAttr(colElement, "align", "left"),
Wrap = GetBoolAttr(colElement, "wrap")
};
ParseColumnWidth(colElement, col);
node.Columns.Add(col);
}
// Parse non-col children (col elements are consumed above as column definitions)
foreach (var child in element.Elements().Where(e => e.Name.LocalName != "col"))
{
var childNode = ParseElement(child);
if (childNode != null)
node.Children.Add(childNode);
}
return node;
}
private static void ApplyFormatting(XElement element, LineNode node)
{
node.Bold = GetBoolAttr(element, "bold");
node.Big = GetBoolAttr(element, "big");
node.Tall = GetBoolAttr(element, "tall");
node.Red = GetBoolAttr(element, "red");
node.LineSpacing = GetNullableIntAttr(element, "lineSpacing");
}
private static void ApplyFormatting(XElement element, ColumnsNode node)
{
node.Bold = GetBoolAttr(element, "bold");
node.Big = GetBoolAttr(element, "big");
node.Tall = GetBoolAttr(element, "tall");
node.Red = GetBoolAttr(element, "red");
node.LineSpacing = GetNullableIntAttr(element, "lineSpacing");
}
private static void ApplyFormatting(XElement element, RowNode node)
{
node.Bold = GetBoolAttr(element, "bold");
node.Big = GetBoolAttr(element, "big");
node.Tall = GetBoolAttr(element, "tall");
node.Red = GetBoolAttr(element, "red");
node.LineSpacing = GetNullableIntAttr(element, "lineSpacing");
}
private static string GetTextContent(XElement element)
{
// Get all inner text content (may include {{}} expressions)
// Use element.Value to get concatenated text of all text nodes
if (!element.HasElements)
return element.Value;
// If element has child elements, only get direct text nodes
return string.Concat(element.Nodes().OfType<XText>().Select(t => t.Value));
}
private static string? GetRequiredAttr(XElement element, string name, string elementName)
{
var attr = element.Attribute(name);
if (attr == null)
throw new TemplateParsingException($"<{elementName}> requires '{name}' attribute");
return attr.Value;
}
private static string GetAttr(XElement element, string name, string? defaultValue)
{
var attr = element.Attribute(name);
return attr?.Value ?? defaultValue ?? "";
}
private static bool GetBoolAttr(XElement element, string name)
{
var attr = element.Attribute(name);
if (attr == null) return false;
return attr.Value.Equals("true", StringComparison.OrdinalIgnoreCase);
}
private static int GetIntAttr(XElement element, string name, int defaultValue)
{
var attr = element.Attribute(name);
if (attr == null) return defaultValue;
return int.TryParse(attr.Value, out var v) ? v : defaultValue;
}
private static int? GetNullableIntAttr(XElement element, string name)
{
var attr = element.Attribute(name);
if (attr == null) return null;
return int.TryParse(attr.Value, out var v) ? v : null;
}
private static void ParseColumnWidth(XElement element, ColumnDef col)
{
var attr = element.Attribute("width");
if (attr == null) return;
var value = attr.Value.Trim();
if (value.EndsWith('%'))
{
if (double.TryParse(value[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out var pct))
col.WidthPercent = pct;
}
else
{
if (int.TryParse(value, out var w))
col.Width = w;
}
}
}