251 lines
7.7 KiB
C#
251 lines
7.7 KiB
C#
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),
|
|
Width = GetNullableIntAttr(colElement, "width"),
|
|
Align = GetAttr(colElement, "align", "left")
|
|
};
|
|
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),
|
|
Width = GetNullableIntAttr(colElement, "width"),
|
|
Align = GetAttr(colElement, "align", "left")
|
|
};
|
|
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;
|
|
}
|
|
}
|