templates support
This commit is contained in:
28
Inspectron.Epson.Templates/Validation/TemplateError.cs
Normal file
28
Inspectron.Epson.Templates/Validation/TemplateError.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Validation;
|
||||
|
||||
public enum TemplateErrorCode
|
||||
{
|
||||
// Syntax errors (RTL001-RTL010)
|
||||
RTL001_UnexpectedToken = 1,
|
||||
RTL002_UnclosedBlock = 2,
|
||||
RTL003_InvalidDirective = 3,
|
||||
RTL004_InvalidBinding = 4,
|
||||
RTL005_InvalidForeachSyntax = 5,
|
||||
RTL006_InvalidCondition = 6,
|
||||
RTL007_UnclosedStyleBlock = 7,
|
||||
RTL008_InvalidColumnSpec = 8,
|
||||
RTL009_InvalidSeparator = 9,
|
||||
RTL010_UnexpectedEndOfFile = 10
|
||||
}
|
||||
|
||||
public record TemplateError(
|
||||
TemplateErrorCode Code,
|
||||
string Message,
|
||||
SourcePosition Position)
|
||||
{
|
||||
public string CodeString => Code.ToString().Split('_')[0];
|
||||
|
||||
public override string ToString() => $"{CodeString}: {Message} at {Position}";
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace Inspectron.Epson.Templates.Validation;
|
||||
|
||||
public record TemplateValidationResult(
|
||||
List<TemplateError> Errors,
|
||||
List<TemplateWarning> Warnings)
|
||||
{
|
||||
public bool IsValid => Errors.Count == 0;
|
||||
public bool HasWarnings => Warnings.Count > 0;
|
||||
|
||||
public static TemplateValidationResult Valid() => new(new List<TemplateError>(), new List<TemplateWarning>());
|
||||
|
||||
public static TemplateValidationResult WithErrors(params TemplateError[] errors) =>
|
||||
new(errors.ToList(), new List<TemplateWarning>());
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (IsValid && !HasWarnings)
|
||||
{
|
||||
return "Template is valid.";
|
||||
}
|
||||
|
||||
var lines = new List<string>();
|
||||
if (Errors.Count > 0)
|
||||
{
|
||||
lines.Add($"Errors ({Errors.Count}):");
|
||||
lines.AddRange(Errors.Select(e => $" {e}"));
|
||||
}
|
||||
if (Warnings.Count > 0)
|
||||
{
|
||||
lines.Add($"Warnings ({Warnings.Count}):");
|
||||
lines.AddRange(Warnings.Select(w => $" {w}"));
|
||||
}
|
||||
return string.Join(Environment.NewLine, lines);
|
||||
}
|
||||
}
|
||||
228
Inspectron.Epson.Templates/Validation/TemplateValidator.cs
Normal file
228
Inspectron.Epson.Templates/Validation/TemplateValidator.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using Inspectron.Epson.Templates.Configuration;
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
using Inspectron.Epson.Templates.Language.Nodes;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Validation;
|
||||
|
||||
public class TemplateValidator
|
||||
{
|
||||
private readonly PrinterProfile? _profile;
|
||||
private readonly List<TemplateError> _errors = new();
|
||||
private readonly List<TemplateWarning> _warnings = new();
|
||||
private readonly HashSet<string> _definedVariables = new();
|
||||
|
||||
public TemplateValidator(PrinterProfile? profile = null)
|
||||
{
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
public TemplateValidationResult Validate(string templateSource)
|
||||
{
|
||||
_errors.Clear();
|
||||
_warnings.Clear();
|
||||
_definedVariables.Clear();
|
||||
|
||||
// Lexer phase
|
||||
var lexer = new Lexer(templateSource);
|
||||
var lexerResult = lexer.Tokenize();
|
||||
|
||||
foreach (var error in lexerResult.Errors)
|
||||
{
|
||||
_errors.Add(new TemplateError(
|
||||
TemplateErrorCode.RTL007_UnclosedStyleBlock,
|
||||
error.Message,
|
||||
error.Position));
|
||||
}
|
||||
|
||||
// Parser phase
|
||||
var parser = new Parser(lexerResult.Tokens);
|
||||
var parseResult = parser.Parse();
|
||||
|
||||
foreach (var error in parseResult.Errors)
|
||||
{
|
||||
var code = MapParseError(error.Message);
|
||||
_errors.Add(new TemplateError(code, error.Message, error.Position));
|
||||
}
|
||||
|
||||
// Semantic validation
|
||||
if (_errors.Count == 0)
|
||||
{
|
||||
ValidateNode(parseResult.Template);
|
||||
}
|
||||
|
||||
return new TemplateValidationResult(_errors.ToList(), _warnings.ToList());
|
||||
}
|
||||
|
||||
private void ValidateNode(ITemplateNode node)
|
||||
{
|
||||
switch (node)
|
||||
{
|
||||
case TemplateNode template:
|
||||
foreach (var child in template.ChildNodes)
|
||||
{
|
||||
ValidateNode(child);
|
||||
}
|
||||
break;
|
||||
|
||||
case IfNode ifNode:
|
||||
ValidateCondition(ifNode.IfBranch.Condition, ifNode.Position);
|
||||
if (ifNode.IfBranch.Body.Count == 0)
|
||||
{
|
||||
_warnings.Add(new TemplateWarning(
|
||||
TemplateWarningCode.RTL100_EmptyBlock,
|
||||
"Empty @if block body",
|
||||
ifNode.Position));
|
||||
}
|
||||
foreach (var child in ifNode.IfBranch.Body)
|
||||
{
|
||||
ValidateNode(child);
|
||||
}
|
||||
foreach (var branch in ifNode.ElseIfBranches)
|
||||
{
|
||||
ValidateCondition(branch.Condition, branch.Position);
|
||||
foreach (var child in branch.Body)
|
||||
{
|
||||
ValidateNode(child);
|
||||
}
|
||||
}
|
||||
if (ifNode.ElseBranch != null)
|
||||
{
|
||||
foreach (var child in ifNode.ElseBranch)
|
||||
{
|
||||
ValidateNode(child);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ForeachNode foreachNode:
|
||||
_definedVariables.Add(foreachNode.ItemVariable);
|
||||
if (foreachNode.Body.Count == 0)
|
||||
{
|
||||
_warnings.Add(new TemplateWarning(
|
||||
TemplateWarningCode.RTL100_EmptyBlock,
|
||||
"Empty @foreach block body",
|
||||
foreachNode.Position));
|
||||
}
|
||||
foreach (var child in foreachNode.Body)
|
||||
{
|
||||
ValidateNode(child);
|
||||
}
|
||||
_definedVariables.Remove(foreachNode.ItemVariable);
|
||||
break;
|
||||
|
||||
case RowNode rowNode:
|
||||
ValidateRowWidth(rowNode);
|
||||
foreach (var column in rowNode.Columns)
|
||||
{
|
||||
ValidateNode(column);
|
||||
}
|
||||
break;
|
||||
|
||||
case ColumnNode columnNode:
|
||||
foreach (var child in columnNode.ContentNodes)
|
||||
{
|
||||
ValidateNode(child);
|
||||
}
|
||||
break;
|
||||
|
||||
case StyledTextNode styledNode:
|
||||
ValidateStyles(styledNode.Styles, styledNode.Position);
|
||||
foreach (var child in styledNode.ContentNodes)
|
||||
{
|
||||
ValidateNode(child);
|
||||
}
|
||||
break;
|
||||
|
||||
case BindingNode bindingNode:
|
||||
ValidateBinding(bindingNode);
|
||||
break;
|
||||
|
||||
case TextNode:
|
||||
case SeparatorNode:
|
||||
case EmptyLineNode:
|
||||
// No additional validation needed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateCondition(string condition, SourcePosition position)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(condition))
|
||||
{
|
||||
_errors.Add(new TemplateError(
|
||||
TemplateErrorCode.RTL006_InvalidCondition,
|
||||
"Empty condition expression",
|
||||
position));
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateBinding(BindingNode node)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(node.Path))
|
||||
{
|
||||
_errors.Add(new TemplateError(
|
||||
TemplateErrorCode.RTL004_InvalidBinding,
|
||||
"Empty binding path",
|
||||
node.Position));
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateStyles(IReadOnlyList<string> styles, SourcePosition position)
|
||||
{
|
||||
var validStyles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"bold", "big", "tall", "red", "center", "right", "left"
|
||||
};
|
||||
|
||||
foreach (var style in styles)
|
||||
{
|
||||
var styleName = style.ToLowerInvariant();
|
||||
if (styleName.StartsWith("spacing:"))
|
||||
{
|
||||
var spacingValue = styleName[8..];
|
||||
if (!int.TryParse(spacingValue, out _))
|
||||
{
|
||||
_warnings.Add(new TemplateWarning(
|
||||
TemplateWarningCode.RTL102_PossibleNullReference,
|
||||
$"Invalid spacing value: '{spacingValue}'",
|
||||
position));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!validStyles.Contains(styleName))
|
||||
{
|
||||
_warnings.Add(new TemplateWarning(
|
||||
TemplateWarningCode.RTL102_PossibleNullReference,
|
||||
$"Unknown style: '{style}'",
|
||||
position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateRowWidth(RowNode row)
|
||||
{
|
||||
if (_profile == null) return;
|
||||
|
||||
var totalWidth = row.Columns.Sum(c => c.Width);
|
||||
if (totalWidth > _profile.LineWidth)
|
||||
{
|
||||
_warnings.Add(new TemplateWarning(
|
||||
TemplateWarningCode.RTL103_LineWidthExceeded,
|
||||
$"Row total width ({totalWidth}) exceeds printer line width ({_profile.LineWidth})",
|
||||
row.Position));
|
||||
}
|
||||
}
|
||||
|
||||
private TemplateErrorCode MapParseError(string message)
|
||||
{
|
||||
if (message.Contains("@end"))
|
||||
return TemplateErrorCode.RTL002_UnclosedBlock;
|
||||
if (message.Contains("foreach"))
|
||||
return TemplateErrorCode.RTL005_InvalidForeachSyntax;
|
||||
if (message.Contains("unexpected", StringComparison.OrdinalIgnoreCase))
|
||||
return TemplateErrorCode.RTL001_UnexpectedToken;
|
||||
|
||||
return TemplateErrorCode.RTL001_UnexpectedToken;
|
||||
}
|
||||
}
|
||||
22
Inspectron.Epson.Templates/Validation/TemplateWarning.cs
Normal file
22
Inspectron.Epson.Templates/Validation/TemplateWarning.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Inspectron.Epson.Templates.Language;
|
||||
|
||||
namespace Inspectron.Epson.Templates.Validation;
|
||||
|
||||
public enum TemplateWarningCode
|
||||
{
|
||||
// Semantic warnings (RTL100-RTL103)
|
||||
RTL100_EmptyBlock = 100,
|
||||
RTL101_UnusedVariable = 101,
|
||||
RTL102_PossibleNullReference = 102,
|
||||
RTL103_LineWidthExceeded = 103
|
||||
}
|
||||
|
||||
public record TemplateWarning(
|
||||
TemplateWarningCode Code,
|
||||
string Message,
|
||||
SourcePosition Position)
|
||||
{
|
||||
public string CodeString => Code.ToString().Split('_')[0];
|
||||
|
||||
public override string ToString() => $"{CodeString}: {Message} at {Position}";
|
||||
}
|
||||
Reference in New Issue
Block a user