using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Text; using UnityEngine; namespace UnityExplorer { public static class ParseUtility { private static readonly HashSet nonPrimitiveTypes = new HashSet { typeof(string), typeof(decimal), typeof(DateTime), }; // Helper for formatting float/double/decimal numbers to maximum of 4 decimal points. // And also for formatting a sequence of those numbers, ie a Vector3, Color etc public static readonly string NumberFormatString = $"0.####"; private static readonly Dictionary numSequenceStrings = new Dictionary(); public static string FormatDecimalSequence(params object[] numbers) { if (numbers.Length <= 0) return null; return string.Format(CultureInfo.CurrentCulture, GetSequenceFormatString(numbers.Length), numbers); } public static string GetSequenceFormatString(int count) { if (count <= 0) return null; if (numSequenceStrings.ContainsKey(count)) return numSequenceStrings[count]; string[] strings = new string[count]; for (int i = 0; i < count; i++) strings[i] = $"{{{i}:{NumberFormatString}}}"; string ret = string.Join(" ", strings); numSequenceStrings.Add(count, ret); return ret; } // Main parsing API public static bool CanParse(Type type) { return !string.IsNullOrEmpty(type?.FullName) && (type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName)); } public static bool TryParse(string input, Type type, out object obj, out Exception parseException) { obj = null; parseException = null; if (type == null) return false; if (type == typeof(string)) { obj = input; return true; } if (type.IsEnum) { try { obj = Enum.Parse(type, input); return true; } catch (Exception ex) { parseException = ex.GetInnerMostException(); return false; } } try { if (customTypes.ContainsKey(type.FullName)) { obj = customTypes[type.FullName].Invoke(input); } else { obj = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs) .Invoke(null, new object[] { input }); } return true; } catch (Exception ex) { ex = ex.GetInnerMostException(); parseException = ex; } return false; } private static readonly HashSet formattedTypes = new HashSet { typeof(float), typeof(double), typeof(decimal) }; public static string ToStringForInput(object obj, Type type) { if (type == null || obj == null) return null; if (type == typeof(string)) return obj as string; if (type.IsEnum) { return Enum.IsDefined(type, obj) ? Enum.GetName(type, obj) : obj.ToString(); } try { if (customTypes.ContainsKey(type.FullName)) { return customTypesToString[type.FullName].Invoke(obj); } else if (formattedTypes.Contains(type)) { return ReflectionUtility.GetMethodInfo(type, "ToString", new Type[] { typeof(string), typeof(IFormatProvider) }) .Invoke(obj, new object[] { NumberFormatString, CultureInfo.CurrentCulture }) as string; } else return obj.ToString(); } catch (Exception ex) { ExplorerCore.LogWarning($"Exception formatting object for input: {ex}"); return null; } } private static readonly Dictionary typeInputExamples = new Dictionary(); public static string GetExampleInput(Type type) { if (!typeInputExamples.ContainsKey(type.AssemblyQualifiedName)) { try { if (type.IsEnum) typeInputExamples.Add(type.AssemblyQualifiedName, Enum.GetNames(type).First()); else { var instance = Activator.CreateInstance(type); typeInputExamples.Add(type.AssemblyQualifiedName, ToStringForInput(instance, type)); } } catch (Exception ex) { ExplorerCore.LogWarning("Exception generating default instance for example input for '" + type.FullName + "'"); ExplorerCore.Log(ex); return ""; } } return typeInputExamples[type.AssemblyQualifiedName]; } #region Custom parse methods internal delegate object ParseMethod(string input); private static readonly Dictionary customTypes = new Dictionary { { typeof(Vector2).FullName, TryParseVector2 }, { typeof(Vector3).FullName, TryParseVector3 }, { typeof(Vector4).FullName, TryParseVector4 }, { typeof(Quaternion).FullName, TryParseQuaternion }, { typeof(Rect).FullName, TryParseRect }, { typeof(Color).FullName, TryParseColor }, { typeof(Color32).FullName, TryParseColor32 }, { typeof(LayerMask).FullName, TryParseLayerMask }, }; internal delegate string ToStringMethod(object obj); private static readonly Dictionary customTypesToString = new Dictionary { { typeof(Vector2).FullName, Vector2ToString }, { typeof(Vector3).FullName, Vector3ToString }, { typeof(Vector4).FullName, Vector4ToString }, { typeof(Quaternion).FullName, QuaternionToString }, { typeof(Rect).FullName, RectToString }, { typeof(Color).FullName, ColorToString }, { typeof(Color32).FullName, Color32ToString }, { typeof(LayerMask).FullName, LayerMaskToString }, }; // Vector2 public static object TryParseVector2(string input) { Vector2 vector = default; var split = input.Split(' '); vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture); vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture); return vector; } public static string Vector2ToString(object obj) { if (!(obj is Vector2 vector)) return null; return FormatDecimalSequence(vector.x, vector.y); } // Vector3 public static object TryParseVector3(string input) { Vector3 vector = default; var split = input.Split(' '); vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture); vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture); vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture); return vector; } public static string Vector3ToString(object obj) { if (!(obj is Vector3 vector)) return null; return FormatDecimalSequence(vector.x, vector.y, vector.z); } // Vector4 public static object TryParseVector4(string input) { Vector4 vector = default; var split = input.Split(' '); vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture); vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture); vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture); vector.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture); return vector; } public static string Vector4ToString(object obj) { if (!(obj is Vector4 vector)) return null; return FormatDecimalSequence(vector.x, vector.y, vector.z, vector.w); } // Quaternion public static object TryParseQuaternion(string input) { Vector3 vector = default; var split = input.Split(' '); if (split.Length == 4) { Quaternion quat = default; quat.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture); quat.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture); quat.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture); quat.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture); return quat; } else { vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture); vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture); vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture); return Quaternion.Euler(vector); } } public static string QuaternionToString(object obj) { if (!(obj is Quaternion quaternion)) return null; Vector3 vector = quaternion.eulerAngles; return FormatDecimalSequence(vector.x, vector.y, vector.z); } // Rect public static object TryParseRect(string input) { Rect rect = default; var split = input.Split(' '); rect.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture); rect.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture); rect.width = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture); rect.height = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture); return rect; } public static string RectToString(object obj) { if (!(obj is Rect rect)) return null; return FormatDecimalSequence(rect.x, rect.y, rect.width, rect.height); } // Color public static object TryParseColor(string input) { Color color = default; var split = input.Split(' '); color.r = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture); color.g = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture); color.b = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture); if (split.Length > 3) color.a = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture); else color.a = 1; return color; } public static string ColorToString(object obj) { if (!(obj is Color color)) return null; return FormatDecimalSequence(color.r, color.g, color.b, color.a); } // Color32 public static object TryParseColor32(string input) { Color32 color = default; var split = input.Split(' '); color.r = byte.Parse(split[0].Trim(), CultureInfo.CurrentCulture); color.g = byte.Parse(split[1].Trim(), CultureInfo.CurrentCulture); color.b = byte.Parse(split[2].Trim(), CultureInfo.CurrentCulture); if (split.Length > 3) color.a = byte.Parse(split[3].Trim(), CultureInfo.CurrentCulture); else color.a = 255; return color; } public static string Color32ToString(object obj) { if (!(obj is Color32 color)) return null; // ints, this is fine return $"{color.r} {color.g} {color.b} {color.a}"; } // Layermask (Int32) public static object TryParseLayerMask(string input) { return (LayerMask)int.Parse(input); } public static string LayerMaskToString(object obj) { if (!(obj is LayerMask mask)) return null; return mask.value.ToString(); } #endregion } }