Files
UnityExplorer_Fix/src/Core/Utility/ParseUtility.cs

415 lines
13 KiB
C#

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<Type> nonPrimitiveTypes = new HashSet<Type>
{
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<int, string> numSequenceStrings = new Dictionary<int, string>();
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<Type> formattedTypes = new HashSet<Type>
{
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<string, string> typeInputExamples = new Dictionary<string, string>();
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<string, ParseMethod> customTypes = new Dictionary<string, ParseMethod>
{
{ 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<string, ToStringMethod> customTypesToString = new Dictionary<string, ToStringMethod>
{
{ 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
}
}