# XML Receipt Template Syntax ## Overview Templates are XML documents that combine static text, data binding expressions, and control flow to produce a list of `PrintCommand` objects for Epson thermal receipt printers. ```csharp var engine = new ReceiptTemplateEngine(); List commands = engine.Render( xmlTemplate, // XML template string jsonData, // JSON data string lineWidth: 42, // characters per line (normal font) bigFontLineWidth: 22 // characters per line (big font) ); ``` --- ## Root Element ### `` Required root element. All other elements must be nested inside it. ```xml ``` --- ## Content Elements ### `` Outputs a single line of text. The most common element. **Attributes:** | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `align` | `left` / `center` / `right` | `left` | Text alignment within the line width | | `bold` | `true` / `false` | `false` | Bold text | | `big` | `true` / `false` | `false` | Double-width font (uses `bigFontLineWidth` for alignment) | | `tall` | `true` / `false` | `false` | Double-height font | | `red` | `true` / `false` | `false` | Red text (on supported printers) | | `lineSpacing` | int | _(none)_ | Override line spacing in dots | | `wrap` | `true` / `false` | `false` | Word-wrap text that exceeds line width | | `wrapIndent` | int | `0` | Indent (in characters) for continuation lines when wrapping | **Examples:** ```xml Hello World RECEIPT {{Title}} --------- {{dish.Number}}x {{dish.Name}} 1x Very Long Dish Name That Will Wrap To Next Line Spaced out text ``` When `big="true"`, alignment uses `bigFontLineWidth` instead of `lineWidth`. --- ### `` Two-column layout: left-aligned left text, right-aligned right text. The line width is split in half. **Attributes:** | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `left` | string | `""` | Left column content (supports `{{}}` expressions) | | `right` | string | `""` | Right column content (supports `{{}}` expressions) | | `bold` | `true` / `false` | `false` | Bold text | | `big` | `true` / `false` | `false` | Double-width font | | `tall` | `true` / `false` | `false` | Double-height font | | `red` | `true` / `false` | `false` | Red text | | `lineSpacing` | int | _(none)_ | Override line spacing | | `wrap` | `true` / `false` | `false` | Wrap left column if combined text exceeds line width | | `wrapIndent` | int | `0` | Indent for wrapped continuation lines | **Examples:** ```xml ``` Without `wrap`, the left column is padded to half the line width and the right column is right-padded to fill the remaining space. With `wrap="true"`, if the combined text exceeds the line width, the left column wraps and the right column appears right-aligned on the first line. --- ### `` Multi-column layout with explicit column definitions. Each column is defined by a nested `` element. **Row attributes:** | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `bold` | `true` / `false` | `false` | Bold text | | `big` | `true` / `false` | `false` | Double-width font | | `tall` | `true` / `false` | `false` | Double-height font | | `red` | `true` / `false` | `false` | Red text | | `lineSpacing` | int | _(none)_ | Override line spacing | **`` attributes:** | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `width` | int or string | _(auto)_ | Column width as a character count (`"10"`) or percentage of the row width (`"33.3%"`). Unspecified columns share remaining space equally. | | `align` | `left` / `center` / `right` | `left` | Text alignment within the column | | `wrap` | `true` / `false` | `false` | Word-wrap text that exceeds column width. All columns expand vertically to match the tallest cell. | **Examples:** ```xml Name Qty Price MwSt % Brutto Netto MwSt {{tax.Category}}:{{tax.Rate}}% {{tax.Gross:F2}} {{tax.Currency}} {{tax.Net:F2}} {{tax.Currency}} {{tax.TaxAmount:F2}} {{tax.Currency}} Very Long Product Name Here 12.50 {{item.Name}} {{item.Description}} ``` In this example, the first three columns are 10 characters wide. The fourth column gets all remaining space (`lineWidth - 30`). When `wrap="true"` is set on a column, text that exceeds the column width wraps to multiple lines. All columns in the row expand vertically to match the tallest cell. Each wrapped line respects the column's `align` attribute. --- ### `` Outputs a line of repeated characters spanning the full line width. **Attributes:** | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `char` | single char | `-` | Character to repeat | **Examples:** ```xml ``` Always uses `lineWidth` (not `bigFontLineWidth`). --- ### `` Triggers a paper cut. Produces a `PrintCommand` with `IsCut = true`. ```xml ``` --- ### `` Outputs one or more empty lines. **Attributes:** | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `lines` | int | `1` | Number of empty lines | **Examples:** ```xml ``` --- ### `` Renders an ASCII box table with borders (`+`, `-`, `|`). Column definitions are provided via nested `` elements. **Attributes:** | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `items` | string | _(none)_ | JSON array path for data rows | | `var` | string | `"item"` | Loop variable name for each data row | | `headerItems` | string | _(none)_ | JSON array path for header rows (optional) | If `headerItems` is not set, the text content of `` elements is used as the header row. Columns without text produce no header. **`` attributes:** | Attribute | Type | Default | Description | |-----------|------|---------|-------------| | `width` | int or string | _(auto)_ | Column width as a character count (`"10"`) or percentage of the content area (`"50%"`). Percentages are relative to the content width (line width minus border characters). Unspecified columns share remaining space equally. | | `align` | `left` / `center` / `right` | `left` | Cell content alignment | | `wrap` | `true` / `false` | `false` | Word-wrap cell text that exceeds column width. All columns in the row expand vertically to match the tallest cell. | Column widths exclude border characters. With 3 columns, 4 border characters (`|`) are used, so the available content width is `lineWidth - 4`. Percentage widths are calculated against this content width. **Examples:** ```xml
Order:QR InfoDate
NamePrice
NamePrice
``` When `wrap="true"` is set on a table ``, cell text that exceeds the column width wraps to multiple lines. All columns in the row expand vertically to match the tallest cell, with empty cells padded with spaces. Each wrapped line respects the column's `align` attribute. --- ## Control Flow ### `` Iterates over a JSON array. For each item, the child elements are rendered with the loop variable available in the data context. **Attributes (both required):** | Attribute | Type | Description | |-----------|------|-------------| | `items` | string | Path to the JSON array (supports dot notation for nested arrays) | | `var` | string | Variable name to bind each array element to | **Loop variables** (available inside ``): | Variable | Type | Description | |----------|------|-------------| | `{{$index}}` | int | Zero-based index of the current item | | `{{$first}}` | bool | `true` for the first item | | `{{$last}}` | bool | `true` for the last item | **Examples:** ```xml {{item.Name}}: {{item.Price:F2}} {{gang.Id}}. {{gang.Name}} {{dish.Number}}x {{dish.Name}} {{$index}}: {{item.Name}} {{gang.Name}} ``` If `items` resolves to `null`, an empty array, or a non-array value, the loop body is skipped entirely. --- ### `` / `` Conditionally renders child elements. An `` block is optional and must immediately follow its corresponding ``. **Attributes:** | Attribute | Type | Description | |-----------|------|-------------| | `test` | string | Condition expression (see Condition Expressions below) | **Examples:** ```xml {{SpecialInstruction}} Discount: {{DiscountInfo.Description}} No discount applied Large order! Active order Modified First item header ``` --- ## Data Binding ### Expression Syntax `{{}}` Expressions are enclosed in double curly braces and can appear in text content, `left`/`right` attributes of ``, and `` text content. **Property access:** ```xml {{Name}} {{Person.Address.City}} {{FirstName}} {{LastName}} Order #{{OrderNumber}} - Table {{TableNumber}} {{item.Name}} - {{item.Price}} ``` Property lookup is **case-insensitive**. Both `{{name}}` and `{{Name}}` resolve the same JSON property. If a property is missing or `null`, the expression resolves to an empty string. ### Format Strings Use a colon after the property name to apply a format string: ``` {{PropertyName:format}} ``` **DateTime formatting** (the JSON value must be an ISO 8601 date string): ```xml {{DateTime:dd.MM.yyyy}} {{DateTime:HH:mm:ss}} {{DateTime:dd-MMM-yy HH:mm}} ``` **Number formatting** (standard .NET format strings): ```xml {{Price:F2}} {{Amount:N0}} ``` ### Loop Variables Available only inside `` blocks: ```xml {{$index}}: {{item.Name}} ``` | Variable | Type | Description | |----------|------|-------------| | `{{$index}}` | int | Zero-based index | | `{{$first}}` | bool | `true` on first iteration | | `{{$last}}` | bool | `true` on last iteration | `$first` and `$last` are primarily useful in `` conditions rather than text output. --- ## Condition Expressions Used in the `test` attribute of ``. The following forms are supported: ### Truthiness A property path by itself checks whether the value is "truthy": ```xml ``` | JSON value | Truthy? | |------------|---------| | `"hello"` | yes | | `""` | no | | `42` | yes | | `0` | no | | `true` | yes | | `false` | no | | `[1, 2]` | yes | | `[]` | no | | `null` | no | | _(missing)_ | no | | `{ ... }` | yes | ### Negation Prefix with `!` to negate: ```xml ``` ### Comparisons Six comparison operators are supported. Operands can be property paths, numeric literals, or quoted string literals: ```xml ``` If both operands parse as numbers, numeric comparison is used. Otherwise, case-insensitive string comparison is used. Comparisons can also be negated: ```xml ``` ### Loop Variables in Conditions ```xml ``` --- ## Complete Example ### Kitchen Receipt Template ```xml {{Title}} {{TransactionDateTime:dd-MMM-yy HH:mm}} Nr.:{{ReceiptNumber}} {{WaiterName}} Tisch: {{TableNumber}} {{SpecialInstruction}} {{gang.Id}}. {{gang.Name}} {{dish.Number}}x {{dish.Name}} Comment: {{dish.Comment}} ``` ### Sample JSON Data ```json { "Title": "Ristorante Bella", "TransactionDateTime": "2024-03-15T14:30:00Z", "ReceiptNumber": "42", "WaiterName": "Max Mustermann", "TableNumber": "5", "SpecialInstruction": null, "Gangs": [ { "Id": 1, "Name": "Vorspeise", "Dishes": [ { "Number": 2, "Name": "Caesar Salad", "Comment": null } ] }, { "Id": 2, "Name": "Hauptgang", "Dishes": [ { "Number": 1, "Name": "Wiener Schnitzel", "Comment": "Well done" } ] } ] } ``` ### Printed Output (42 char width) ``` Ristorante Bella ------------------------------------------ 15-Mar-24 14:30 Nr.:42 Max Mustermann Tisch: 5 ------------------------------------------ 1. Vorspeise 2x Caesar Salad ------------------------------------------ 2. Hauptgang 1x Wiener Schnitzel Comment: Well done ------------------------------------------ ``` --- ## Quick Reference | Element | Produces | Key Attributes | |---------|----------|----------------| | `` | _(root)_ | - | | `` | 1 PrintCommand (or N if wrapping) | `align`, `bold`, `big`, `tall`, `red`, `wrap`, `wrapIndent`, `lineSpacing` | | `` | 1 PrintCommand (or N if wrapping) | `left`, `right`, `bold`, `big`, `tall`, `red`, `wrap`, `wrapIndent`, `lineSpacing` | | `` | 1+ PrintCommand(s) (N if wrapping) | `bold`, `big`, `tall`, `red`, `lineSpacing` + nested `` | | `` | 1 PrintCommand | `char` | | `` | 1 PrintCommand (IsCut=true) | - | | `` | N PrintCommands (empty lines) | `lines` | | `` | N x children | `items` (required), `var` (required) | | `` | 0 or children | `test` (required) | | `` | 0 or children | _(must follow ``)_ | | `` | Multiple PrintCommands (bordered) | `items`, `var`, `headerItems` + nested `` |