Files
Disco/Disco.Services/Expressions/Expression.cs
T
2025-07-20 12:00:57 +10:00

314 lines
12 KiB
C#

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 System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace Disco.Services.Expressions
{
public sealed class Expression : List<IExpressionPart>
{
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("DeviceExt", typeof(Extensions.DeviceExt));
TypeRegistry.RegisterType("EmailExt", typeof(Extensions.EmailExt));
TypeRegistry.RegisterType("ImageExt", typeof(Extensions.ImageExt));
TypeRegistry.RegisterType("UserExt", typeof(Extensions.UserExt));
}
public T EvaluateFirst<T>(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;
}
public Tuple<string, bool, object> Evaluate(object ExpressionContext, IDictionary Variables)
{
if (Count == 0)
return new Tuple<string, bool, object>(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<string, bool, object>(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<string, bool, object>(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<string, object>();
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<string, string>)new Dictionary<string, string>() : new LazyDictionary(() => detailsService.GetDetails(targetJob.User)));
}
else if (target is Device targetDevice)
{
detailsVariables.Add("UserDetails", targetDevice.AssignedUser == null ? (IDictionary<string, string>)new Dictionary<string, string>() : new LazyDictionary(() => detailsService.GetDetails(targetDevice.AssignedUser)));
}
}
return new Hashtable(detailsVariables)
{
{
"DataContext",
Database
},
{
"User",
User
},
{
"TimeStamp",
TimeStamp
},
{
"AttachmentType",
AttachmentType
},
{
"State",
DocumentState
}
};
}
public static Dictionary<string, string> StandardVariableTypes()
{
return new Dictionary<string, string>
{
{
"#DataContext",
typeof(DiscoDataContext).AssemblyQualifiedName
},
{
"#User",
typeof(User).AssemblyQualifiedName
},
{
"#TimeStamp",
typeof(DateTime).AssemblyQualifiedName
},
{
"#AttachmentType",
typeof(DocumentTemplate).AssemblyQualifiedName
},
{
"#State",
typeof(DocumentState).AssemblyQualifiedName
},
{
"#UserDetails",
typeof(Dictionary<string, string>).AssemblyQualifiedName
},
};
}
public static Dictionary<string, string> ExtensionLibraryTypes()
{
return new Dictionary<string, string>
{
{
"DataExt",
typeof(Extensions.DataExt).AssemblyQualifiedName
},
{
"DeviceExt",
typeof(Extensions.DeviceExt).AssemblyQualifiedName
},
{
"EmailExt",
typeof(Extensions.EmailExt).AssemblyQualifiedName
},
{
"ImageExt",
typeof(Extensions.ImageExt).AssemblyQualifiedName
},
{
"UserExt",
typeof(Extensions.UserExt).AssemblyQualifiedName
}
};
}
}
}