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