28 KiB
Receipt Template Language (RTL) - Project Specification
Phase 1: Core Template Engine
1. Overview
A file-based template system for configuring receipt layouts per receipt type and printer model. Replaces hardcoded C# converters with editable .template files.
Deliverables
- New project:
Inspectron.Epson.Templates - Template language lexer, parser, and interpreter
- File-based configuration loader
- Integration with existing
IReceiptConverterFactory - Fallback template for undefined combinations
- Unit tests
2. Project Structure
Inspectron.Epson.Templates/
├── Inspectron.Epson.Templates.csproj
├── Language/
│ ├── Lexer.cs
│ ├── Parser.cs
│ ├── Tokens.cs
│ └── Nodes/
│ ├── TemplateNode.cs
│ ├── TextNode.cs
│ ├── StyledTextNode.cs
│ ├── BindingNode.cs
│ ├── SeparatorNode.cs
│ ├── RowNode.cs
│ ├── ColumnNode.cs
│ ├── IfNode.cs
│ ├── ForeachNode.cs
│ └── EmptyLineNode.cs
├── Interpreter/
│ ├── TemplateInterpreter.cs
│ ├── DataContext.cs
│ └── InterpreterException.cs
├── Configuration/
│ ├── TemplateConfiguration.cs
│ ├── TemplateAssignment.cs
│ ├── PrinterProfile.cs
│ ├── PrinterProfileRegistry.cs
│ └── TemplateResolver.cs
├── Storage/
│ ├── ITemplateStorage.cs
│ └── FileTemplateStorage.cs
├── TemplateEngine.cs
├── TemplateReceiptConverter.cs
└── TemplateReceiptConverterFactory.cs
3. File System Layout
templates/
├── kitchen-default.template # Default kitchen receipt
├── kitchen-u220.template # Kitchen receipt for TM-U220II
├── bar-default.template # Bar receipt
├── final-receipt.template # Customer receipt
├── orders-overview.template # Orders overview
├── fallback.template # Used when no match found
├── assignments.json # Maps (receiptType, printer) → template
└── samples/ # Sample data for preview/testing
├── kitchen-default.json
├── bar-default.json
├── final-receipt.json
└── orders-overview.json
4. Template Language Specification
4.1 Line Types
| Line Type | Syntax | Example |
|---|---|---|
| Styled Text | #styles# content |
#bold,center# {title} |
| Plain Text | content |
Hello World |
| Separator | ---, ===, ***, ~~~ |
--- |
| Row Start | @row ratios [styles] |
@row 60:40 bold |
| Row Content | content | content |
{name} | >{price} |
| Row End | @endrow |
@endrow |
| Condition | @if condition |
@if comment |
| Loop | @foreach item in collection |
@foreach dish in dishes |
| Block End | @end |
@end |
| Empty | (blank line) |
4.2 Styles
| Style | Description |
|---|---|
bold |
Bold text |
big |
Large font (2x magnification) |
red |
Red color (two-color printers only) |
center |
Center-aligned |
right |
Right-aligned |
spacing:N |
Set line spacing (0-255) |
spacing:default |
Reset to default spacing |
Syntax: #style1,style2,...# content
Examples:
#bold,center,big# {title}
#red# WARNING
#spacing:0,bold# Compact line
4.3 Data Binding
Syntax: {path} or {path:format}
Path navigation:
{title} // Root property
{order.date} // Nested property
{items.count} // Collection count
Format specifiers:
{price:F2} // Decimal, 2 places → "12.50"
{date:dd-MMM-yy} // Date → "25-Jan-26"
{date:HH:mm} // Time → "14:30"
{number:D4} // Padded integer → "0042"
Missing data behavior: Returns empty string (silent).
4.4 Separators
--- // Dashed line (fills line width)
=== // Equals line
*** // Asterisk line
~~~ // Tilde line
Minimum 3 characters. Fills PrinterProfile.LineWidth.
4.5 Rows (Columns)
Multi-line syntax:
@row 60:40
{leftContent} | >{rightContent}
@endrow
Single-line shorthand:
@row 60:40 | {left} | >{right} |
Ratios:
@row 50:50 // Two equal columns
@row 60:30:10 // Three columns (percentages)
@row *:20 // First fills remainder, second is 20%
@row 30:*:20 // Middle fills remainder
Column alignment (prefix in content):
{text} // Left-aligned (default)
>{text} // Right-aligned
^{text} // Center-aligned
Row styles (optional, applied to all columns):
@row 50:50 bold
@row 70:30 big,red
4.6 Conditionals
Syntax:
@if condition
content
@end
Conditions:
@if propertyName // Truthy (not null, not empty, not false, not 0)
@if !propertyName // Falsy
@if items.count > 0 // Comparison
@if status == "active" // String equality
@if total >= 100 // Numeric comparison
Operators: ==, !=, >, <, >=, <=
4.7 Loops
Syntax:
@foreach item in collection
content using {item.property}
@end
Loop variables:
{item} // Current item (if primitive)
{item.property} // Item property
{item._index} // 0-based index
{item._number} // 1-based number
{item._first} // true if first iteration
{item._last} // true if last iteration
Nesting:
@foreach gang in gangs
#red# {gang.name}
@foreach dish in gang.dishes
{dish.number}x {dish.name}
@end
@end
5. Configuration
5.1 assignments.json
{
"assignments": [
{
"receiptType": "Receipt",
"printerProfile": null,
"templateFile": "final-receipt.template"
},
{
"receiptType": "WorkareaTicket",
"printerProfile": null,
"templateFile": "kitchen-default.template"
},
{
"receiptType": "WorkareaTicket",
"printerProfile": "tm-u220ii",
"templateFile": "kitchen-u220.template"
},
{
"receiptType": "NextCourse",
"printerProfile": null,
"templateFile": "kitchen-default.template"
},
{
"receiptType": "OrdersOverview",
"printerProfile": null,
"templateFile": "orders-overview.template"
}
],
"fallbackTemplate": "fallback.template"
}
5.2 Printer Profiles (built-in)
| Profile ID | Printer | LineWidth | BigLineWidth | SupportsRed |
|---|---|---|---|---|
tm-t30iii |
TM-T30III | 48 | 24 | false |
tm-u220ii |
TM-U220II | 33 | 20 | true |
Mapped from printer ID byte:
0x01→tm-t30iii0x13→tm-u220ii
5.3 Resolution Priority
- Exact match:
receiptType+printerProfile - Type only:
receiptType+printerProfile: null - Fallback:
fallbackTemplate
6. Public API
6.1 TemplateEngine
namespace Inspectron.Epson.Templates;
public class TemplateEngine
{
public TemplateEngine(string templatesDirectory);
/// <summary>
/// Renders a template with the given data.
/// </summary>
public List<PrintCommand> Render(
string templateContent,
string jsonData,
PrinterProfile profile);
/// <summary>
/// Parses template and returns errors/warnings (for validation).
/// </summary>
public TemplateValidationResult Validate(string templateContent);
}
public class TemplateValidationResult
{
public bool IsValid { get; }
public List<TemplateError> Errors { get; }
public List<TemplateWarning> Warnings { get; }
}
public class TemplateError
{
public int Line { get; }
public int Column { get; }
public string Message { get; }
public string Code { get; } // e.g., "RTL001"
}
public class TemplateWarning
{
public int Line { get; }
public int Column { get; }
public string Message { get; }
public string Code { get; } // e.g., "RTL100"
}
6.2 TemplateReceiptConverterFactory
namespace Inspectron.Epson.Templates;
public class TemplateReceiptConverterFactory : IReceiptConverterFactory
{
public TemplateReceiptConverterFactory(string templatesDirectory);
/// <summary>
/// Creates a converter for the given receipt type and printer.
/// Falls back to fallback template if no match found.
/// </summary>
public IReceiptConverter Create(int receiptType, byte printerId);
/// <summary>
/// Reloads templates and assignments from disk.
/// </summary>
public void Reload();
}
6.3 Integration Example
// In EpsonPrintService startup
var converterFactory = new TemplateReceiptConverterFactory("./templates");
// In DI registration (Ninject)
kernel.Bind<IReceiptConverterFactory>()
.ToConstant(converterFactory)
.InSingletonScope();
// Usage remains unchanged
var converter = converterFactory.Create(receiptType, printerId);
var commands = converter.Convert(jsonContent);
7. Error Codes
Errors (prevent rendering)
| Code | Message |
|---|---|
| RTL001 | Unexpected token '{token}' at line {line} |
| RTL002 | Unclosed block '@{block}' started at line {line} |
| RTL003 | '@end' without matching '@if' or '@foreach' |
| RTL004 | Invalid style '{style}' |
| RTL005 | Invalid ratio format '{ratio}' |
| RTL006 | Column count mismatch: expected {expected}, got {actual} |
| RTL007 | Invalid condition syntax |
| RTL008 | Invalid binding path '{path}' |
| RTL009 | Unclosed binding '{' at line {line} |
| RTL010 | Unclosed style marker '#' at line {line} |
Warnings (render anyway)
| Code | Message |
|---|---|
| RTL100 | Unused loop variable '{variable}' |
| RTL101 | Empty @if block |
| RTL102 | Empty @foreach block |
| RTL103 | Style '{style}' has no effect on this printer |
8. Default Templates
Provide default templates matching current hardcoded behavior:
kitchen-default.template- matchesKitchenReceiptConverterkitchen-u220.template- compact version for narrow printersbar-default.template- matchesBarReceiptConverterfinal-receipt.template- matchesFinalReceiptConverterorders-overview.template- matchesOrderItemsReceiptConverterfallback.template- minimal template that dumps JSON fields
9. Testing
Unit Tests
Inspectron.Epson.Templates.Tests/
├── Lexer/
│ ├── LexerBasicTests.cs
│ ├── LexerStyleTests.cs
│ ├── LexerBindingTests.cs
│ └── LexerControlFlowTests.cs
├── Parser/
│ ├── ParserBasicTests.cs
│ ├── ParserRowTests.cs
│ ├── ParserNestingTests.cs
│ └── ParserErrorTests.cs
├── Interpreter/
│ ├── InterpreterTextTests.cs
│ ├── InterpreterBindingTests.cs
│ ├── InterpreterConditionTests.cs
│ ├── InterpreterLoopTests.cs
│ └── InterpreterRowTests.cs
├── Configuration/
│ ├── TemplateResolverTests.cs
│ └── PrinterProfileTests.cs
└── Integration/
├── KitchenReceiptTests.cs
├── FinalReceiptTests.cs
└── FallbackTests.cs
Test Coverage Goals
- Lexer: All token types, edge cases, error handling
- Parser: All node types, nesting, malformed input
- Interpreter: All features, format specifiers, missing data
- Integration: Match output of existing converters
10. Migration Plan
- Create
Inspectron.Epson.Templatesproject - Implement and test template engine
- Create default templates matching current output
- Add
TemplateReceiptConverterFactoryto DI as alternative - Integration test: compare output of both factories
- Switch DI registration to use template factory
- Remove legacy converter code (optional, can keep as reference)
Phase 2: Template Editor
1. Overview
Desktop application for creating and editing receipt templates with live preview, syntax highlighting, autocomplete, and validation.
Deliverables
- Avalonia desktop application:
Inspectron.Epson.TemplateEditor - Syntax highlighting for RTL
- Autocomplete (styles, control flow, data bindings)
- Real-time validation with error/warning display
- Live preview with sample data
- Template assignment management UI
2. Project Structure
Inspectron.Epson.TemplateEditor/
├── Inspectron.Epson.TemplateEditor.csproj
├── App.axaml
├── App.axaml.cs
├── Program.cs
├── ViewModels/
│ ├── MainViewModel.cs
│ ├── EditorViewModel.cs
│ ├── PreviewViewModel.cs
│ ├── AssignmentsViewModel.cs
│ └── TemplateListViewModel.cs
├── Views/
│ ├── MainWindow.axaml
│ ├── EditorView.axaml
│ ├── PreviewView.axaml
│ ├── AssignmentsView.axaml
│ └── TemplateListView.axaml
├── Controls/
│ ├── ReceiptPreviewControl.cs
│ └── ValidationMargin.cs
├── Editor/
│ ├── RtlSyntaxHighlighting.cs
│ ├── RtlCompletionProvider.cs
│ ├── RtlFoldingStrategy.cs
│ └── JsonSchemaExtractor.cs
├── Services/
│ ├── TemplateService.cs
│ ├── SampleDataService.cs
│ └── ValidationService.cs
└── Resources/
└── RTL.xshd
3. UI Layout
┌─────────────────────────────────────────────────────────────────────────────┐
│ File Edit View Tools Help │
├─────────────────────────────────────────────────────────────────────────────┤
│ Templates │ │
│ ┌────────────────────┐ │ ┌─────────────────────────┬───────────────────────┐ │
│ │ 📄 kitchen-default │ │ │ Template Editor │ Preview │ │
│ │ 📄 kitchen-u220 │ │ │ │ │ │
│ │ 📄 bar-default │ │ │ 1│#bold,center,big# │ ┌───────────────────┐ │ │
│ │ 📄 final-receipt │ │ │ 2│{title} │ │ Warme Küche │ │ │
│ │ 📄 orders-overview │ │ │ 3│--- │ │───────────────────│ │ │
│ │ 📄 fallback │ │ │ 4│ │ │ │ │ │
│ │ │ │ │ 5│@row 60:40 │ │ 25-Jan Nr.:42 │ │ │
│ │ ──────────────────│ │ │ 6│{date} | >{number} │ │ │ │ │
│ │ 📋 Assignments │ │ │ 7│@endrow │ │ Max Mustermann │ │ │
│ │ │ │ │ 8│#center# {waiter} │ │ │ │ │
│ └────────────────────┘ │ │ 9│ │ │ Tisch: 12 │ │ │
│ │ │ 10│@foreach dish in... │ │───────────────────│ │ │
│ Sample Data │ │ 11│ {dish.number}x... │ │ 2x Tomatensuppe │ │ │
│ ┌────────────────────┐ │ │ 12│@end │ │ 1x Schnitzel │ │ │
│ │kitchen-default.json│ │ │ │ │ - Pommes │ │ │
│ │ │ │ │ │ │ + Reis │ │ │
│ │ [Edit Sample] │ │ │ │ └───────────────────┘ │ │
│ └────────────────────┘ │ │ │ │ │
│ │ │ │ Printer: [TM-T30III▼]│ │
├────────────────────────┴─┴─────────────────────────┴───────────────────────┴─┤
│ Problems │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ ⚠ Line 11: Unused loop variable 'dish' in nested scope │ │
│ │ ❌ Line 15: Unclosed '@foreach' block started at line 10 │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────────┤
│ ✓ Saved │ kitchen-default.template │ Ln 8, Col 15 │ 2 warnings, 1 error │
└──────────────────────────────────────────────────────────────────────────────┘
4. Features
4.1 Template List Panel
- List all
.templatefiles fromtemplates/directory - Click to open in editor
- Right-click context menu: Rename, Delete, Duplicate
- "New Template" button
- "Assignments" entry opens assignment editor
4.2 Editor Panel (AvaloniaEdit)
Syntax Highlighting:
| Element | Color |
|---|---|
Style markers #...# |
Blue |
Bindings {...} |
Teal |
Control flow @if, @foreach, @end, @row, @endrow |
Purple |
Separators --- |
Gray |
Column delimiters | |
Yellow |
Alignment markers >, ^ |
Orange |
Code Folding:
- Collapse
@if...@endblocks - Collapse
@foreach...@endblocks - Collapse
@row...@endrowblocks
Autocomplete Triggers:
| Trigger | Shows |
|---|---|
# |
Style keywords |
, (inside #...#) |
Style keywords |
@ (at line start) |
Control flow keywords |
{ |
Root data properties + loop variables |
. (inside {...}) |
Child properties |
: (inside {...}) |
Format specifiers |
in (after @foreach x) |
Collection properties |
Validation:
- Real-time parsing (debounced 300ms)
- Error squiggles (red underline)
- Warning squiggles (yellow underline)
- Gutter icons (❌ error, ⚠ warning)
- Click error → jump to line
4.3 Preview Panel
Receipt Preview Control:
- Renders
List<PrintCommand>visually - Mimics thermal receipt appearance
- Monospace font
- Configurable width based on selected printer
- Updates live as template changes (debounced)
Printer Selector:
- Dropdown: TM-T30III, TM-U220II
- Changes
LineWidthandBigLineWidth - Shows/hides red color support
4.4 Sample Data Panel
- Shows current sample JSON file
- "Edit Sample" button → opens JSON in editor (modal or panel)
- JSON syntax highlighting
- Validates JSON on save
- Shows extracted schema (tree view of available properties)
4.5 Problems Panel
- Lists all errors and warnings
- Grouped by severity (errors first)
- Each item shows: icon, line number, message
- Double-click → jump to location in editor
- Filterable: Show errors only, Show all
4.6 Assignments Editor
┌─────────────────────────────────────────────────────────────────────┐
│ Template Assignments │
├─────────────────────────────────────────────────────────────────────┤
│ ┌─────────────┬──────────────────┬─────────────────────┬──────────┐ │
│ │ Receipt Type│ Printer │ Template │ Actions │ │
│ ├─────────────┼──────────────────┼─────────────────────┼──────────┤ │
│ │ Receipt │ (Any) │ final-receipt ▼│ [Delete] │ │
│ │ WorkareaTicket│ (Any) │ kitchen-default ▼│ [Delete] │ │
│ │ WorkareaTicket│ TM-U220II │ kitchen-u220 ▼│ [Delete] │ │
│ │ NextCourse │ (Any) │ kitchen-default ▼│ [Delete] │ │
│ │ OrdersOverview│ (Any) │ orders-overview ▼│ [Delete] │ │
│ └─────────────┴──────────────────┴─────────────────────┴──────────┘ │
│ │
│ [+ Add Assignment] │
│ │
│ Fallback Template: [fallback.template ▼] │
│ │
│ ⚠ Warning: No specific template for Receipt + TM-U220II │
│ │
│ [Cancel] [Save] │
└─────────────────────────────────────────────────────────────────────┘
5. Data Flow
┌──────────────┐
│ Template │──────┐
│ (.template) │ │
└──────────────┘ │
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Sample JSON │──▶│ Template │──▶│ PrintCommands│
│ (.json) │ │ Engine │ │ │
└──────────────┘ └──────────────┘ └──────┬───────┘
│ │
│ ▼
│ ┌──────────────┐
│ │ Preview │
│ │ Renderer │
▼ └──────────────┘
┌──────────────┐
│ Validation │
│ Results │
└──────────────┘
6. Autocomplete Implementation
6.1 Context Detection
public enum CompletionContext
{
None,
Style, // Inside #...#
ControlFlow, // After @ at line start
Binding, // Inside {...}
BindingProperty, // After . inside {...}
BindingFormat, // After : inside {...}
ForeachCollection, // After "in" in @foreach
RowContent // Inside @row...@endrow
}
6.2 Schema Extraction
public class JsonSchemaExtractor
{
public SchemaNode Extract(string json);
}
public abstract record SchemaNode(string Name);
public record ObjectNode(string Name, List<SchemaNode> Properties) : SchemaNode(Name);
public record ArrayNode(string Name, SchemaNode ItemType) : SchemaNode(Name);
public record ValueNode(string Name, ValueType Type) : SchemaNode(Name);
public enum ValueType { String, Number, Boolean, DateTime, Unknown }
6.3 Scope Tracking
Track active @foreach loops to resolve loop variable bindings:
public record LoopScope(string VariableName, string CollectionPath, SchemaNode ItemSchema);
public class ScopeTracker
{
public List<LoopScope> GetActiveScopes(string templateText, int cursorPosition);
}
7. File Operations
| Action | Behavior |
|---|---|
| New Template | Prompt for name, create empty file, open in editor |
| Open Template | Load from disk, show in editor |
| Save | Write to disk, trigger reload hint for print service |
| Save As | Prompt for new name, save copy |
| Delete | Confirm dialog, remove file |
| Rename | Prompt for new name, rename file, update assignments |
| Duplicate | Prompt for new name, copy content |
8. Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Ctrl+S |
Save current template |
Ctrl+N |
New template |
Ctrl+O |
Open templates folder |
Ctrl+Shift+P |
Toggle preview panel |
Ctrl+Space |
Trigger autocomplete |
F2 |
Rename selected template |
Delete |
Delete selected template |
Ctrl+G |
Go to line |
Ctrl+F |
Find in template |
Ctrl+H |
Find and replace |
F8 |
Go to next problem |
Shift+F8 |
Go to previous problem |
9. Configuration
Editor settings stored in editor-settings.json:
{
"templatesDirectory": "./templates",
"theme": "dark",
"fontSize": 14,
"showLineNumbers": true,
"wordWrap": false,
"autoSave": true,
"autoSaveDelayMs": 2000,
"previewDebounceMs": 300,
"lastOpenedTemplate": "kitchen-default.template"
}
10. Dependencies
<ItemGroup>
<!-- Avalonia -->
<PackageReference Include="Avalonia" Version="11.*" />
<PackageReference Include="Avalonia.Desktop" Version="11.*" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.*" />
<!-- Editor -->
<PackageReference Include="AvaloniaEdit" Version="11.*" />
<!-- MVVM -->
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
<!-- Project Reference -->
<ProjectReference Include="../Inspectron.Epson.Templates/Inspectron.Epson.Templates.csproj" />
</ItemGroup>
11. Testing
Manual Test Cases
-
Basic Editing
- Create new template
- Edit with syntax highlighting
- Save and verify file content
-
Autocomplete
- Type
#→ shows styles - Type
{→ shows data properties - Type
{dish.inside foreach → shows dish properties + loop vars
- Type
-
Validation
- Missing
@end→ shows error - Unused loop variable → shows warning
- Fix error → error disappears
- Missing
-
Preview
- Edit template → preview updates
- Switch printer → width changes
- Missing data → shows empty (no crash)
-
Assignments
- Add new assignment
- Change template mapping
- Delete assignment
- Save and verify JSON
12. Timeline
Phase 1: Core Engine (Foundation)
- Project setup and structure
- Lexer implementation + tests
- Parser implementation + tests
- Interpreter implementation + tests
- Configuration and resolver
- File storage
- Factory integration
- Default templates
- Integration testing
- Migration and deployment
Phase 2: Editor (After Phase 1 Complete)
- Avalonia project setup
- Basic UI layout (panels, splitters)
- Template list with file operations
- Editor integration with AvaloniaEdit
- Syntax highlighting
- Basic preview rendering
- Validation display
- Autocomplete - styles and control flow
- Autocomplete - data bindings with scope
- Assignments editor
- Sample data editor
- Polish and testing