using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using Newtonsoft.Json.Linq; using TextFieldParserStandard; namespace InABox.Core { [UserTracking("Digital Forms")] public class DigitalForm : Entity, IRemotable, IPersistent, ILicense//, IDuplicatable { /// /// The following functions support PNG, BMP and JPEG /// /// /// private static readonly byte[] bmpHeader = Encoding.ASCII.GetBytes("BM"); private static readonly byte[] pngHeader = { 137, 80, 78, 71 }; private static readonly byte[] jpegHeader = { 255, 216, 255, 224 }; private static readonly byte[] jpeg2Header = { 255, 216, 255, 225 }; [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)] [EditorSequence(1)] public string Code { get; set; } [TextBoxEditor] [EditorSequence(2)] public string Description { get; set; } [ComboLookupEditor(typeof(DigitalFormCategoryLookups))] [EditorSequence(3)] public string AppliesTo { get; set; } [CheckBoxEditor] [EditorSequence(4)] public bool Active { get; set; } [CheckBoxEditor] [EditorSequence(5)] public bool Secure { get; set; } [EditorSequence(6)] public DigitalFormGroupLink Group { get; set; } [NullEditor] public string Report { get; set; } public IEntityDuplicator GetDuplicator() { var result = new EntityDuplicator(); result.AddChild(x => x.Form); result.AddChild(x => x.Form); return result; } protected override void Init() { base.Init(); Active = true; Secure = false; Group = new DigitalFormGroupLink(); } public override string ToString() { return string.Format("{0}: {1}", Code, Description); } private static bool IsValidImage(byte[] data) { return bmpHeader.SequenceEqual(data.Take(bmpHeader.Length)) || pngHeader.SequenceEqual(data.Take(pngHeader.Length)) || jpegHeader.SequenceEqual(data.Take(jpegHeader.Length)) || jpeg2Header.SequenceEqual(data.Take(jpeg2Header.Length)); } private static bool IsValidImage(string base64Data) { var firstBytes = base64Data.Substring(0, Math.Min(8, base64Data.Length)); return IsValidImage(Convert.FromBase64String(firstBytes)); } /// /// Takes a serialized FormData string and BlobData string and deserializes into one dictionary. /// The result of this is just as if you were deserializing formData as per usual. /// /// /// /// public static Dictionary? DeserializeFormData(string formData, string? blobData) { var values = Serialization.Deserialize>(formData); if (values is null) return null; if(blobData != null) { var blobs = Serialization.Deserialize>(blobData); if(blobs != null) { var updates = new List>(); foreach (var (key, value) in values) { if ((value is string str && blobs.TryGetValue(str, out var blob)) || (value is Guid guid && blobs.TryGetValue(guid.ToString(), out blob))) { updates.Add(new Tuple(key, blob)); } } foreach(var (key, value) in updates) { values[key] = value; } } } return values; } /// /// Like , but takes the FormData and BlobData from , /// rather than having to specify them manually. /// /// /// public static Dictionary? DeserializeFormData(IDigitalFormInstance form) => DeserializeFormData(form.FormData, form.BlobData); /// /// Takes a form instance, set of variables and some saved values, and serializes the FormData and BlobData into the respective /// fields of . /// /// /// Doesn't return anything, but saves the serialized results directly into form.FormData and form.BlobData. /// It choose which fields should be saved into BlobData based on whether the form field has the interface. ///
/// should be the same as what you pass into . ///
/// The form instance. /// The variables associated with the digital form. /// The values to save. public static void SerializeFormData(IDigitalFormInstance form, ICollection variables, Dictionary values) { var blob = new Dictionary(); foreach (var variable in variables) { if (variable.IsBlob() && values.TryGetValue(variable.Code, out var value)) { var id = Guid.NewGuid().ToString(); blob[id] = value; values[variable.Code] = id; } } form.FormData = Serialization.Serialize(values); form.BlobData = Serialization.Serialize(blob); } private static string? GetVariableData(DigitalFormVariable variable, object value) { // TODO: Replace with a function on DFLayoutField or something var fieldType = variable.FieldType(); if (fieldType == typeof(DFLayoutEmbeddedImage) || fieldType == typeof(DFLayoutSignaturePad)) { if (value is byte[]) return IsValidImage((byte[])value) ? Convert.ToBase64String((byte[])value) : null; var str = value.ToString(); return IsValidImage(str) ? str : null; } else if(fieldType == typeof(DFLayoutMultiImage)) { if (value is JArray || value is IEnumerable) return Serialization.Serialize(value); return null; } return variable.FormatValue(value); } /// /// Generates a database FormData from a dictionary of objects. Returns null if the data is invalid (specifically if /// any required fields are not present. /// /// .NET objects /// The variables of the form needed to be encoded /// A string with JSON-encoded FormData, or null if validation requirements are not met. public static string? GenerateFormData(Dictionary values, IEnumerable variables, Entity entity) { var data = new Dictionary(); foreach (var variable in variables) if (values.TryGetValue(variable.Code, out var value)) { var properties = variable.CreateProperties(); if (!string.IsNullOrWhiteSpace(properties.Property)) { if (variable.FieldType() == typeof(DFLayoutLookupField)) { if (values.TryGetValue($"{variable.Code}$ID", out var idStr) && Guid.TryParse((string)idStr, out var id)) CoreUtils.SetPropertyValue(entity, properties.Property, id); } else { CoreUtils.SetPropertyValue(entity, properties.Property, value); } } if (value != null) { var varData = GetVariableData(variable, value); if (varData != null) data[variable.Code] = varData; } } else if (variable.Required) { return null; } return Serialization.Serialize(data); } /// /// Creates a dictionary of objects from a database FormData /// /// A string with JSON-encoded FormData /// The variables of the form needed to be encoded /// public static Dictionary ParseFormData(string formData, IEnumerable variables, Entity entity) { var data = new Dictionary(); // Could be null var formObject = Serialization.Deserialize>(formData); foreach (var variable in variables) { object? value = null; var code = variable.Code; var properties = variable.CreateProperties(); if (!string.IsNullOrWhiteSpace(properties.Property)) { value = CoreUtils.GetPropertyValue(entity, properties.Property); if (variable.FieldType() == typeof(DFLayoutLookupField)) { code = variable.Code + "$ID"; } } else if (value == null) formObject?.TryGetValue(variable.Code, out value); if (value != null) { data[code] = variable.ParseValue(value); } } return data; } } }