219 lines
6.5 KiB
C#
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
|
|
};
|
|
}
|
|
}
|