using System; using System.Globalization; using System.Linq.Expressions; using System.Reflection; using System.Text.RegularExpressions; using AntDesign.Core.Reflection; namespace AntDesign.Core.Helpers { internal static class Formatter { private static readonly Lazy> _formatFunc = new Lazy>(GetFormatLambda, true); public static string Format(T source, string format) { return _formatFunc.Value.Invoke(source, format); } private static Func GetFormatLambda() { var sourceType = typeof(T); var sourceProperty = Expression.Parameter(typeof(T)); var formatString = Expression.Parameter(typeof(string)); Expression variable = sourceProperty; Expression body = Expression.Call(sourceProperty, typeof(object).GetMethod(nameof(ToString))); Expression hasValueExpression = sourceType.IsValueType ? (Expression)Expression.Constant(true) : Expression.NotEqual(sourceProperty, Expression.Default(sourceType)); Expression parsedFormatString = formatString; if (sourceType == typeof(TimeSpan)) { parsedFormatString = Expression.Call(typeof(Formatter).GetMethod(nameof(ParseSpanTimeFormatString), BindingFlags.NonPublic | BindingFlags.Static), formatString); } if (TypeDefined.IsNullable) { sourceType = TypeDefined.NullableType; hasValueExpression = Expression.Equal(Expression.Property(sourceProperty, "HasValue"), Expression.Constant(true)); variable = Expression.Condition(hasValueExpression, Expression.Property(sourceProperty, "Value"), Expression.Default(sourceType)); } if (sourceType.IsSubclassOf(typeof(IFormattable))) { var method = sourceType.GetMethod(nameof(ToString), new[] { typeof(string), typeof(IFormatProvider) }); body = Expression.Call(Expression.Convert(variable, sourceType), method, parsedFormatString, Expression.Constant(null)); } else { var method = sourceType.GetMethod(nameof(ToString), new[] { typeof(string) }); if (method != null) { body = Expression.Call(Expression.Convert(variable, sourceType), method, parsedFormatString); } } var condition = Expression.Condition(hasValueExpression, body, Expression.Constant(string.Empty)); return Expression.Lambda>(condition, sourceProperty, formatString).Compile(); } /// /// parse other characters in format string. /// /// refer to https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings#other-characters /// /// private static string ParseSpanTimeFormatString(string format) { if (string.IsNullOrWhiteSpace(format)) { return format; } return Regex.Replace(format, "[^d|^h|^m|^s|^f|^F]+", "'$0'"); } } internal static class Formatter { /// /// under WASM mode, when format a double number to percentage, there will be a blank between number and %, '35.00 %' /// use this method instead to avoid the blank space /// /// public static string ToPercentWithoutBlank(double num) { return num.ToString("p", CultureInfo.InvariantCulture).Replace(" ", "", StringComparison.Ordinal); } } }