using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Documents; using Disco.Models.Services.Expressions.Extensions; using Disco.Services.Plugins.Features.DetailsProvider; using Spring.Core.TypeResolution; using Spring.Expressions; using System; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Text; namespace Disco.Services.Expressions { public sealed class Expression : List { public string Name { get; } public string Source { get; } public bool IsDynamic { get; private set; } public int Ordinal { get; } public bool IsRequired { get; } public bool IsReadOnly { get; } public RectangleF? Position { get; } private Expression(string name, string source, int ordinal, bool isRequired, bool isReadOnly, RectangleF? position) { Name = name; Source = source; Ordinal = ordinal; IsRequired = isRequired; IsReadOnly = isReadOnly; Position = position; } public static void InitializeExpressions() { TypeRegistry.RegisterType("DataExt", typeof(Extensions.DataExt)); TypeRegistry.RegisterType("UserExt", typeof(Extensions.UserExt)); TypeRegistry.RegisterType("DeviceExt", typeof(Extensions.DeviceExt)); TypeRegistry.RegisterType("ImageExt", typeof(Extensions.ImageExt)); } public T EvaluateFirst(object ExpressionContext, IDictionary Variables) { T result = default(T); if (Count > 0) { try { object expressionResult = this[0].Evaluate(ExpressionContext, Variables); if (expressionResult != null) { if (expressionResult is T) { result = (T)expressionResult; } else { throw new InvalidOperationException("Expression returned an invalid type"); } } } catch (Exception ex) { throw new InvalidOperationException("Expression evaluation resulted in an error", ex); } } return result; } internal static IExpression Parse(string source) { throw new NotImplementedException(); } public Tuple Evaluate(object ExpressionContext, IDictionary Variables) { if (Count == 0) return new Tuple(string.Empty, false, null); if (!IsDynamic) { if (Count != 1) throw new InvalidOperationException("Non-dynamic expressions should only have one part"); if (this[0] is TextExpressionPart textPart) return new Tuple(textPart.RawSource, false, null); else throw new InvalidOperationException("Non-dynamic expressions should have a single TextExpressionPart component"); } var resultValue = new StringBuilder(); object resultObject = null; bool resultError = false; foreach (var expressionPart in this) { try { object partValue = expressionPart.Evaluate(ExpressionContext, Variables); if (partValue != null) { // Check for Result Objects if (partValue is IImageExpressionResult) resultObject = partValue; else resultValue.Append(partValue.ToString()); } } catch (Exception ex) { if (!expressionPart.ErrorsAllowed) { resultValue.Append("## ERROR # "); resultValue.Append(ex.Message); resultValue.Append(" ##"); resultError = true; } } } return new Tuple(resultValue.ToString(), resultError, resultObject); } public static Expression TokenizeSingleDynamic(string Name, string ExpressionSource, int Ordinal) { var e = new Expression(Name, ExpressionSource, Ordinal, isRequired: false, isReadOnly: false, position: null); if (ExpressionSource != null && !string.IsNullOrWhiteSpace(ExpressionSource)) e.Add(new EvaluateExpressionPart(ExpressionSource)); e.IsDynamic = true; return e; } public static Expression Tokenize(string Name, string ExpressionSource, int Ordinal, bool IsRequired, bool IsReadOnly) => Tokenize(Name, ExpressionSource, Ordinal, IsRequired, IsReadOnly, null); public static Expression Tokenize(string name, string expressionSource, int ordinal, bool isRequired, bool isReadOnly, RectangleF? position) { var e = new Expression(name, expressionSource, ordinal, isRequired, isReadOnly, position); if (!expressionSource.Contains("{") || !expressionSource.Contains("}")) { e.Add(new TextExpressionPart(expressionSource)); } else { var token = new StringBuilder(); bool tokenEval = false; int tokenEvalDepth = 0; foreach (char c in expressionSource) { switch (c) { case '{': { if (!tokenEval) { if (token.Length > 0) { e.Add(new TextExpressionPart(token.ToString())); token = new StringBuilder(); } tokenEval = true; tokenEvalDepth = 0; } tokenEvalDepth++; token.Append(c); break; } case '}': { token.Append(c); if (tokenEval) { tokenEvalDepth--; if (tokenEvalDepth <= 0) { if (token.Length != 2 && (token.Length != 3 || token[1] != '@')) { e.Add(new EvaluateExpressionPart(token.ToString())); e.IsDynamic = true; token = new StringBuilder(); } tokenEval = false; } } break; } default: { token.Append(c); break; } } } if (token.Length > 0) { e.Add(new TextExpressionPart(token.ToString())); } } return e; } public static IDictionary StandardVariables(DocumentTemplate AttachmentType, DiscoDataContext Database, User User, DateTime TimeStamp, DocumentState DocumentState, IAttachmentTarget target = null) { var detailsVariables = new Dictionary(); var detailsService = new DetailsProviderService(Database); if (target != null) { if (target is User targetUser) { detailsVariables.Add("UserDetails", new LazyDictionary(() => detailsService.GetDetails(targetUser))); } else if (target is Job targetJob) { detailsVariables.Add("UserDetails", targetJob.User == null ? (IDictionary)new Dictionary() : new LazyDictionary(() => detailsService.GetDetails(targetJob.User))); } else if (target is Device targetDevice) { detailsVariables.Add("UserDetails", targetDevice.AssignedUser == null ? (IDictionary)new Dictionary() : new LazyDictionary(() => detailsService.GetDetails(targetDevice.AssignedUser))); } } return new Hashtable(detailsVariables) { { "DataContext", Database }, { "User", User }, { "TimeStamp", TimeStamp }, { "AttachmentType", AttachmentType }, { "State", DocumentState } }; } public static Dictionary StandardVariableTypes() { return new Dictionary { { "#DataContext", typeof(DiscoDataContext).AssemblyQualifiedName }, { "#User", typeof(User).AssemblyQualifiedName }, { "#TimeStamp", typeof(DateTime).AssemblyQualifiedName }, { "#AttachmentType", typeof(DocumentTemplate).AssemblyQualifiedName }, { "#State", typeof(DocumentState).AssemblyQualifiedName }, { "#UserDetails", typeof(Dictionary).AssemblyQualifiedName }, }; } public static Dictionary ExtensionLibraryTypes() { return new Dictionary { { "DataExt", typeof(Extensions.DataExt).AssemblyQualifiedName }, { "DeviceExt", typeof(Extensions.DeviceExt).AssemblyQualifiedName }, { "ImageExt", typeof(Extensions.ImageExt).AssemblyQualifiedName }, { "UserExt", typeof(Extensions.UserExt).AssemblyQualifiedName } }; } } }