Files
Print_server/Inspectron.Epson.Templates/Validation/TemplateValidator.cs
2026-01-21 15:56:37 +01:00

229 lines
7.2 KiB
C#

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;
}
}