Files
Print_server/Inspectron.Epson.TemplateEngine/DataBinding/ExpressionEvaluator.cs
2026-02-03 14:27:13 +01:00

219 lines
6.5 KiB
C#

using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace Inspectron.Epson.TemplateEngine.DataBinding;
public class ExpressionEvaluator
{
private static readonly Regex ExpressionPattern = new(@"\{\{(.+?)\}\}", RegexOptions.Compiled);
private readonly DataContext _context;
public ExpressionEvaluator(DataContext context)
{
_context = context;
}
public string Evaluate(string template)
{
if (string.IsNullOrEmpty(template))
return "";
return ExpressionPattern.Replace(template, match =>
{
var expression = match.Groups[1].Value.Trim();
return ResolveExpression(expression);
});
}
private string ResolveExpression(string expression)
{
// Loop variables: $index, $first, $last
if (expression.StartsWith("$"))
{
var loopVar = _context.GetLoopVariable(expression);
return loopVar?.ToString() ?? "";
}
// Format string: PropertyName:format
string? format = null;
var colonIndex = expression.IndexOf(':');
if (colonIndex > 0)
{
format = expression[(colonIndex + 1)..];
expression = expression[..colonIndex];
}
var element = _context.Resolve(expression);
if (element == null)
return "";
if (format != null)
return FormatValue(element.Value, format);
return DataContext.GetStringValue(element.Value);
}
private static string FormatValue(JsonElement element, string format)
{
if (element.ValueKind == JsonValueKind.String)
{
var str = element.GetString();
if (str != null && DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dt))
{
return dt.ToString(format, CultureInfo.InvariantCulture);
}
return str ?? "";
}
if (element.ValueKind == JsonValueKind.Number)
{
if (element.TryGetDecimal(out var d))
return d.ToString(format, CultureInfo.InvariantCulture);
}
return DataContext.GetStringValue(element);
}
public bool EvaluateCondition(string test)
{
if (string.IsNullOrWhiteSpace(test))
return false;
// Negation
bool negate = false;
var expr = test.Trim();
if (expr.StartsWith("!"))
{
negate = true;
expr = expr[1..].Trim();
}
// Loop variables: $first, $last
if (expr.StartsWith("$"))
{
var loopVar = _context.GetLoopVariable(expr);
bool loopResult = loopVar is bool b ? b : loopVar != null;
return negate ? !loopResult : loopResult;
}
// Comparison operators
var comparisonOps = new[] { "==", "!=", ">=", "<=", ">", "<" };
foreach (var op in comparisonOps)
{
var parts = SplitComparison(expr, op);
if (parts != null)
{
bool compResult = EvaluateComparison(parts.Value.left, op, parts.Value.right);
return negate ? !compResult : compResult;
}
}
// Truthiness check - property exists and has a value
var result = IsTruthy(expr);
return negate ? !result : result;
}
private (string left, string right)? SplitComparison(string expr, string op)
{
var idx = expr.IndexOf(op, StringComparison.Ordinal);
if (idx < 0) return null;
// Make sure we don't confuse == with = or != with !
if (op == "=" && idx > 0 && expr[idx - 1] == '!') return null;
if (op == ">" && idx > 0 && (expr[idx - 1] == '>' || expr[idx - 1] == '<')) return null;
var left = expr[..idx].Trim();
var right = expr[(idx + op.Length)..].Trim();
if (string.IsNullOrEmpty(left) || string.IsNullOrEmpty(right))
return null;
return (left, right);
}
private bool EvaluateComparison(string left, string op, string right)
{
var leftVal = ResolveComparisonValue(left);
var rightVal = ResolveComparisonValue(right);
// Try numeric comparison
if (decimal.TryParse(leftVal, CultureInfo.InvariantCulture, out var leftNum)
&& decimal.TryParse(rightVal, CultureInfo.InvariantCulture, out var rightNum))
{
return op switch
{
"==" => leftNum == rightNum,
"!=" => leftNum != rightNum,
">" => leftNum > rightNum,
"<" => leftNum < rightNum,
">=" => leftNum >= rightNum,
"<=" => leftNum <= rightNum,
_ => false
};
}
// String comparison
int cmp = string.Compare(leftVal, rightVal, StringComparison.OrdinalIgnoreCase);
return op switch
{
"==" => cmp == 0,
"!=" => cmp != 0,
">" => cmp > 0,
"<" => cmp < 0,
">=" => cmp >= 0,
"<=" => cmp <= 0,
_ => false
};
}
private string ResolveComparisonValue(string value)
{
// Quoted string literal
if ((value.StartsWith("'") && value.EndsWith("'")) ||
(value.StartsWith("\"") && value.EndsWith("\"")))
{
return value[1..^1];
}
// Numeric literal
if (decimal.TryParse(value, CultureInfo.InvariantCulture, out _))
{
return value;
}
// Loop variable
if (value.StartsWith("$"))
{
var loopVar = _context.GetLoopVariable(value);
return loopVar?.ToString() ?? "";
}
// Property resolution
var element = _context.Resolve(value);
if (element == null)
return "";
return DataContext.GetStringValue(element.Value);
}
private bool IsTruthy(string expression)
{
var element = _context.Resolve(expression);
if (element == null)
return false;
return element.Value.ValueKind switch
{
JsonValueKind.Null => false,
JsonValueKind.Undefined => false,
JsonValueKind.False => false,
JsonValueKind.String => !string.IsNullOrEmpty(element.Value.GetString()),
JsonValueKind.Number => element.Value.GetDouble() != 0,
JsonValueKind.Array => element.Value.GetArrayLength() > 0,
_ => true
};
}
}