using System; using System.Collections.Generic; using System.Globalization; using System.Linq; namespace AppConv.General; abstract class DecimalUnitConverterBase<T> : IUnitType where T : struct { internal sealed class DecimalFuncMap : Dictionary<T, Func<decimal, decimal>> { public DecimalFuncMap() {} public DecimalFuncMap(int capacity) : base(capacity) {} } internal sealed class NameMap : Dictionary<string, T> { public NameMap() {} public NameMap(int capacity) : base(capacity) {} } protected abstract NameMap Names { get; } protected abstract DecimalFuncMap ConvertTo { get; } protected abstract DecimalFuncMap ConvertFrom { get; } protected virtual int Precision => 0; protected virtual bool CaseCheck => false; protected virtual NumberStyles NumberStyle => NumberStyles.Float & ~NumberStyles.AllowLeadingSign; protected virtual string ProcessSrc(string src) { return src; } protected virtual string ProcessDst(string dst) { return dst; } protected abstract bool IsValueInvalid(T value); protected virtual decimal Convert(decimal value, T from, T to) { return ConvertFrom[to](ConvertTo[from](value)); } protected virtual string Format(decimal value) { if (Precision > 0) { decimal truncated = decimal.Truncate(value); if (value == truncated) { return truncated.ToString(CultureInfo.InvariantCulture); } } int decimalPlaces = Precision; if (Math.Abs(value) < 1M) { double fractionalPart = (double) Math.Abs(value % 1M); int fractionalZeroCount = -(int) Math.Ceiling(Math.Log(fractionalPart, 10D)); decimalPlaces = Math.Min(28, fractionalZeroCount + Precision); } string result = decimal.Round(value, decimalPlaces, MidpointRounding.ToEven).ToString(CultureInfo.InvariantCulture); if (decimalPlaces > 0) { result = result.TrimEnd('0').TrimEnd('.'); } return result; } public bool TryProcess(string src, string dst, out string result) { src = ProcessSrc(src); dst = ProcessDst(dst); var pairs = new KeyValuePair<string, T>[2]; for (int index = 0; index < 2; index++) { string str = index == 0 ? src : dst; if (CaseCheck) { List<KeyValuePair<string, T>> list = Names.Where(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCultureIgnoreCase) && (str.Length == kvp.Key.Length || !char.IsLetter(str[str.Length - kvp.Key.Length - 1]))).ToList(); if (list.Count == 1) { pairs[index] = list[0]; } else { pairs[index] = list.FirstOrDefault(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCulture)); } } else { pairs[index] = Names.FirstOrDefault(kvp => str.EndsWith(kvp.Key, StringComparison.InvariantCultureIgnoreCase) && (str.Length == kvp.Key.Length || !char.IsLetter(str[str.Length - kvp.Key.Length - 1]))); } if (IsValueInvalid(pairs[index].Value)) { result = string.Empty; return false; } if (index == 0) { src = src[..^pairs[index].Key.Length].TrimEnd(); } } if (decimal.TryParse(src, NumberStyle, CultureInfo.InvariantCulture, out decimal value)) { result = Format(Convert(value, pairs[0].Value, pairs[1].Value)); return true; } else { result = string.Empty; return false; } } }