JD2022-TU1/main/tools/framework/JD.Serialization/Serializers/Xml/VerboseXmlSerializer.cs

862 lines
33 KiB
C#

using System;
using System.Linq;
using System.Xml;
using System.ComponentModel;
using System.Reflection;
using System.Collections;
using System.Drawing;
using System.Collections.Generic;
using System.IO;
using System.Globalization;
using JD.Core;
namespace JD.Serialization
{
/// <summary>
/// The keywords used by the VerboseXmlSerializer.
/// </summary>
internal static class KeyWords
{
public static readonly string c_valueTag = "value";
public static readonly string c_valueNameTag = "valueName";
public static readonly string c_readonlyTag = "readonly";
public static readonly string c_typeTag = "type";
public static readonly string c_enumTag = "Enum";
public static readonly string c_flagsTag = "Flags";
public static readonly string c_validValueTag = "VALIDVALUE";
public static readonly string c_itemTag = "ITEM";
public static readonly string c_countTag = "Count";
public static readonly string c_keyTag = "KEY";
public static readonly string c_valTag = "VAL";
public static readonly string c_listTag = "List";
public static readonly string c_mapTag = "Map";
public static readonly string c_multiMapTag = "MultiMap";
public static readonly string c_factoryDescriptionTag = "FACTORY_DESCRIPTION";
}
/// <summary>
/// XML extensions useful for the VerboseXmlSerializer.
/// </summary>
internal static class XmlExtensions
{
/// <summary>
/// A value node is a node that has a "value" attribute.
/// </summary>
public static bool IsValueNode(this XmlNode _xmlNode)
{
return _xmlNode.Attributes[KeyWords.c_valueTag] != null;
}
/// <summary>
/// Get the value of a "value node". this must be a "value node".
/// </summary>
public static string GetNodeValue(this XmlNode _xmlNode)
{
return _xmlNode.Attributes[KeyWords.c_valueTag].Value;
}
/// <summary>
/// Case-insensitive function to get a child by name.
/// </summary>
public static XmlNode GetChildNodeByName(this XmlNode _xmlNode, string _childNodeName)
{
foreach (XmlNode child in _xmlNode.ChildNodes)
{
if (child.Name.Equals(_childNodeName, StringComparison.OrdinalIgnoreCase))
return child;
}
return null;
}
/// <summary>
/// Get the first child that is not a "value node". Returns null otherwise.
/// Note: Also skip the "factory description" child.
/// </summary>
public static XmlNode GetFirstNonValueChildOrNull(this XmlNode _xmlNode)
{
foreach (XmlNode child in _xmlNode.ChildNodes)
{
if (!child.IsValueNode() && !child.Name.Equals(KeyWords.c_factoryDescriptionTag, StringComparison.OrdinalIgnoreCase))
return child;
}
return null;
}
/// <summary>
/// Find first child named _childNodeName and returns its value. Returns null otherwise.
/// </summary>
public static string GetChildNodeValue(this XmlNode _xmlNode, string _childNodeName)
{
XmlNode child = _xmlNode.GetChildNodeByName(_childNodeName);
if (child != null)
{
return child.Attributes[KeyWords.c_valueTag].Value;
}
return null;
}
/// <summary>
/// Find first child named _childNodeName and returns its boolean value.
/// Returns false if child not found. Does not check the type of the child.
/// </summary>
public static bool GetChildNodeBoolValue(this XmlNode _xmlNode, string _childNodeName)
{
XmlNode child = _xmlNode.GetChildNodeByName(_childNodeName);
if (child != null)
{
return child.Attributes[KeyWords.c_valueTag].Value == "1";
}
return false;
}
/// <summary>
/// Returns the boolean value of an attribute. Returns false if attribute not found.
/// </summary>
public static bool GetBoolAttribute(this XmlNode _xmlNode, string _attributeName)
{
XmlAttribute attribute = _xmlNode.Attributes[_attributeName];
return (attribute != null && attribute.Value == "1");
}
public static string GetAttributeValue(this XmlNode _xmlNode, string _attribute, string _default)
{
XmlAttribute atr = _xmlNode.Attributes[_attribute];
return (atr == null) ? _default : atr.Value;
}
}
/// <summary>
/// An XML serializer that contains a lot of optionnal informations. There is at
/// least one element per property (in the non-verbose version, properties are
/// serialized as fields.
/// Example:
/// <Actor>
/// <globalPos value="4.188156 -0.433773 0.000000" readonly="1" type="Vec3d"/>
/// <RELATIVEZ value="0.000000" type="float" />
/// ...
/// </Actor>
/// </summary>
public class VerboseXmlSerializer : Serializer
{
/// <summary>
/// The XML document the serializer is currently working with.
/// </summary>
private XmlDocument m_doc;
/// <summary>
/// Load in place flag: update the loaded object instead of re-creating it.
/// </summary>
private bool m_loadInPlace = false;
/// <summary>
/// The Serialize will resolve c# types based on their names. This is the Assembly list in which it will look.
/// </summary>
public List<Assembly> AssembliesToSearchForType { get; private set; }
public VerboseXmlSerializer()
: this(JD.Serialization.TypeMap.Instance)
{
}
public VerboseXmlSerializer(ITypeMap alloc)
: base(alloc)
{
AssembliesToSearchForType = new List<Assembly> { typeof(LuaSerializer).Assembly };
}
public object LoadObjectFromXmlString(string xmlContent)
{
m_doc = new XmlDocument();
m_doc.LoadXml(xmlContent);
return LoadObjectFromXmlDocument();
}
public void FillObjectFromXmlString(object objToFill, string xmlContent)
{
m_doc = new XmlDocument();
m_doc.LoadXml(xmlContent);
m_loadInPlace = true;
try
{
XmlNode node = m_doc["root"];
if (node != null)
{
node = node.FirstChild;
if (node != null)
FillObjectFromXmlNode(objToFill, node);
}
}
finally
{
m_loadInPlace = false;
}
}
public string FillXmlStringFromObject(object obj)
{
if (BuildXmlDocumentFromObject(obj))
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = "\t";
settings.OmitXmlDeclaration = false;
settings.Encoding = System.Text.Encoding.GetEncoding("ISO-8859-1");
var stringBuilder = new StringWriter();
using (XmlWriter writer = XmlTextWriter.Create(stringBuilder, settings))
{
System.Xml.XPath.XPathNavigator navigator = m_doc.CreateNavigator();
writer.WriteNode(navigator, true);
}
return stringBuilder.ToString();
}
return "";
}
private Type FindType(string name)
{
string fullName = "JD.Serialization." + name;
var result = Type.GetType(fullName);
if (result == null)
{
foreach (var assembly in AssembliesToSearchForType)
{
result = assembly.GetType(fullName);
if (result != null)
{
return result;
}
}
}
return result;
}
protected override object LoadObjectFromFileImplementation(string file, Type type)
{
m_doc = new XmlDocument();
m_doc.Load(file);
return LoadObjectFromXmlDocument();
}
protected object LoadObjectFromXmlDocument()
{
XmlNode node = m_doc["root"];
if (node != null)
{
node = node.FirstChild;
if (node != null)
return CreateObjectFromXmlNode(node);
}
return null;
}
/// <summary>
/// Find the type of an XML element. Sets innerXmlNode to the child element containing the value.
/// </summary>
private Type GetTypeFromObjectXmlNode(XmlNode node, Type baseType, out XmlNode innerXmlNode)
{
// Early out for basic types
if (node == null || BasicTypeList.IsBasicType(baseType))
{
innerXmlNode = node;
return baseType;
}
// First, look for the NAME element
string xmlTypeName = node.GetAttributeValue(KeyWords.c_typeTag, null);
// Then look at the name of the first child
innerXmlNode = node.GetFirstNonValueChildOrNull();
if (xmlTypeName == null && innerXmlNode != null)
xmlTypeName = innerXmlNode.Name;
// Check that the type exists
Type xmlType = FindType(xmlTypeName);
if (xmlType != null && !baseType.IsAssignableFrom(xmlType))
throw new Exception("VerboseXmlSerializer: Cannot use type " + xmlTypeName + " as " + baseType.Name + ".");
if (xmlType == null)
return baseType;
return xmlType;
}
private float ExtractFloatFromString(string str, int index)
{
int beginPos = 0;
int endPos = str.IndexOf(' ', beginPos);
while (index > 0)
{
if (endPos >= 0)
{
beginPos = endPos + 1;
endPos = str.IndexOf(' ', beginPos);
index--;
}
else
{
break;
}
}
if (endPos < 0)
return float.Parse(str.Substring(beginPos), CultureInfo.InvariantCulture);
return float.Parse(str.Substring(beginPos, endPos - beginPos), CultureInfo.InvariantCulture);
}
private object CreateBaseTypeObject(string val, Type targetType)
{
if (targetType.IsEnum)
{
int result;
if (!int.TryParse(val, out result))
{
// The enum has been written as string
// Start by extracting the name of the value in the enum
int lastSeparator = val.LastIndexOf(':');
if (lastSeparator >= 0)
val = val.Substring(lastSeparator + 1);
// Then find the value
try
{
return Convert.ToInt32(Enum.Parse(targetType, val));
}
catch (Exception)
{
if (System.Attribute.IsDefined(targetType, typeof(FlagsAttribute)))
result = (int)0;
else
result = Convert.ToInt32(Activator.CreateInstance(targetType));
}
}
return result;
}
if (targetType == typeof(bool))
return int.Parse(val, CultureInfo.InvariantCulture) != 0;
if (targetType == typeof(char) || targetType == typeof(Char))
{
char result;
if (!char.TryParse(val, out result))
result = (char)0;
return result;
}
if (targetType == typeof(Int16))
return Int16.Parse(val, CultureInfo.InvariantCulture);
if (targetType == typeof(int) || targetType == typeof(Int32))
return int.Parse(val, CultureInfo.InvariantCulture);
if (targetType == typeof(UInt16))
return UInt16.Parse(val, CultureInfo.InvariantCulture);
if (targetType == typeof(uint))
return uint.Parse(val, CultureInfo.InvariantCulture);
if (targetType == typeof(float) || targetType == typeof(Single))
return float.Parse(val, CultureInfo.InvariantCulture);
if (targetType == typeof(double))
return double.Parse(val, CultureInfo.InvariantCulture);
if (targetType == typeof(ObjectPath))
return new ObjectPath(val);
if (targetType == typeof(Vec2d))
{
float x = ExtractFloatFromString(val, 0);
float y = ExtractFloatFromString(val, 1);
return new Vec2d(x, y);
}
if (targetType == typeof(Vec3d))
{
float x = ExtractFloatFromString(val, 0);
float y = ExtractFloatFromString(val, 1);
float z = ExtractFloatFromString(val, 3);
return new Vec3d(x, y, z);
}
if (targetType == typeof(Angle))
{
float degrees = float.Parse(val, CultureInfo.InvariantCulture);
return new Angle((float)((double)degrees), true);
}
object obj = null;
if (targetType == typeof(String8) || targetType == typeof(String) || targetType == typeof(Path) || targetType == typeof(Platform))
{
obj = val;
}
else if (targetType == typeof(Color) || targetType == typeof(ColorInteger) || targetType == typeof(StringID))
{
uint num;
if (uint.TryParse(val, out num))
obj = num;
else
obj = val;
}
else if (targetType == typeof(LocalisationId))
{
uint id = uint.Parse(val, CultureInfo.InvariantCulture);
if (id > uint.MaxValue)
obj = uint.MaxValue;
else
obj = (uint)id;
}
else if (targetType == typeof(Volume))
{
obj = float.Parse(val, CultureInfo.InvariantCulture);
}
if (obj == null)
return null;
TypeConverter conv = TypeDescriptor.GetConverter(targetType);
if (conv.CanConvertFrom(obj.GetType()))
{
return conv.ConvertFrom(obj);
}
return Convert.ChangeType(obj, targetType);
}
public object CreateObjectFromXmlNode(XmlNode node)
{
Type objType = Type.GetType("JD.Serialization." + node.Name);
return CreateObjectFromXmlNode(node, TypeMap.ToRuntimeType(objType));
}
public object CreateObjectFromXmlNode(XmlNode node, Type targetType)
{
if (BasicTypeList.IsBasicType(targetType))
{
string stringValue = node.GetNodeValue();
if (stringValue != null)
return CreateBaseTypeObject(stringValue, targetType);
throw new Exception("VerboseXmlSerializer.CreateObjectFromXmlNode: Basic type node " + node.Name + " has no value.");
}
else
{
object obj;
if (!targetType.IsAbstract)
{
obj = Activator.CreateInstance(targetType);
FillObjectFromXmlNode(obj, node);
}
else
{
obj = null;
}
return obj;
}
}
private void FillObjectFromXmlNode(object objToFill, XmlNode node)
{
Type objType = objToFill.GetType();
foreach (var property in SerializableFields(objType))
{
string propertyName = FormatPropertyName(property);
if (!ShouldSerialize(property))
continue;
// Find the XML node corresponding to the property
XmlNode propXmlNode = node.GetChildNodeByName(propertyName);
if (propXmlNode == null)
{
// Values defaulting at null and missing from the XML must stay null
if (property.GetValue(objToFill) != null)
{
TypeMap.ProcessEmptyField(objToFill, property);
}
}
else
{
// Create or update the property
if (BasicTypeList.IsBasicType(property.PropertyType))
{
// Always create new objects for basic types
object propertyValue = CreateObjectFromXmlNode(propXmlNode, property.PropertyType);
property.SetValue(objToFill, propertyValue, null);
}
else if (IsCollection(property.PropertyType))
{
// Never re-create the collections so that we can safely listen to them
var collection = (IUAFCollection)property.GetValue(objToFill, null);
FillCollectionFromXmlNode(collection, propXmlNode, propertyName);
}
else
{
// Fill or re-create sub-objects depending on the "load in place" flag
object propertyValue = null;
if (m_loadInPlace)
{
propertyValue = property.GetValue(objToFill, null);
}
XmlNode innerXmlNode;
Type xmlType = GetTypeFromObjectXmlNode(propXmlNode, property.PropertyType, out innerXmlNode);
if (propertyValue != null && innerXmlNode != null && propertyValue.GetType() == xmlType)
{
FillObjectFromXmlNode(propertyValue, innerXmlNode);
}
else
{
propertyValue = CreateObjectFromXmlNode(innerXmlNode, xmlType);
property.SetValue(objToFill, propertyValue, null);
}
}
// Set the readonly flag
if (objToFill is IPropertiesMetaData)
{
var propertyBrowserObject = (IPropertiesMetaData)objToFill;
propertyBrowserObject.SetPropertyMetaData(propertyName, "readonly", propXmlNode.GetBoolAttribute(KeyWords.c_readonlyTag));
}
}
}
}
private void FillCollectionFromXmlNode(IUAFCollection collection, XmlNode node, string collectionName)
{
if (!m_loadInPlace)
collection.Clear();
if (collection is IUAFDictionary)
{
IUAFDictionary uafDictionnary = collection as IUAFDictionary;
bool isBasicType = BasicTypeList.IsBasicType(uafDictionnary.ValueType);
if (IsCollection(uafDictionnary.KeyType))
throw new Exception("VerboseXmlSerializer.FillCollectionFromXmlNode: Maps indexed by collections are not supported (" + collectionName + ").");
if (uafDictionnary.KeyType.IsAbstract)
throw new Exception("VerboseXmlSerializer.FillCollectionFromXmlNode: Maps indexed by abstract types are not supported (" + collectionName + ").");
// Store all the keys of the collections so that we know which ones have been updated
Type myKeyType = uafDictionnary.KeyType.IsEnum ? typeof(int) : uafDictionnary.KeyType;
Type openKeyListType = typeof(List<>);
Type genericKeyListType = openKeyListType.MakeGenericType(myKeyType);
IList keysNotInXml = Activator.CreateInstance(genericKeyListType) as IList;
foreach (ISerializableKeyValuePair pair in uafDictionnary.SerializableElements)
keysNotInXml.Add(pair.Key);
foreach (XmlNode itemNode in node.ChildNodes)
{
if (!itemNode.Name.Equals(KeyWords.c_itemTag, StringComparison.OrdinalIgnoreCase))
continue;
XmlNode keyXmlNode = itemNode.GetChildNodeByName(KeyWords.c_keyTag);
XmlNode valueXmlNode = itemNode.GetChildNodeByName(KeyWords.c_valTag);
object itemKey = (keyXmlNode == null ?
Activator.CreateInstance(uafDictionnary.KeyType) :
CreateObjectFromXmlNode(keyXmlNode, uafDictionnary.KeyType));
bool keyAlreadyInDictionnary = uafDictionnary.ContainsKey(itemKey);
XmlNode innerXmlNode;
Type xmlType = GetTypeFromObjectXmlNode(valueXmlNode, uafDictionnary.ValueType, out innerXmlNode);
object itemValue = null;
if (keyAlreadyInDictionnary && !isBasicType)
{
// Get the existing object to update in the dictionnary
itemValue = uafDictionnary.GetValue(itemKey);
if (itemValue.GetType() != xmlType)
itemValue = null;
}
if (itemValue != null && innerXmlNode != null)
{
FillObjectFromXmlNode(itemValue, innerXmlNode);
}
else
{
itemValue = (innerXmlNode != null ?
CreateObjectFromXmlNode(innerXmlNode, xmlType) :
Activator.CreateInstance(uafDictionnary.ValueType));
if (keyAlreadyInDictionnary)
uafDictionnary.SetValue(itemKey, itemValue); // Update item
else
uafDictionnary.AddValue(itemKey, itemValue); // Add new item
}
if (innerXmlNode != null)
//uafDictionnary.SetPropertyMetaData(itemKey, PropertiesMetaDataName.ReadOnly, innerXmlNode.GetBoolAttribute(KeyWords.c_readonlyTag));
uafDictionnary.SetPropertyMetaData(itemKey, "readonly", innerXmlNode.GetBoolAttribute(KeyWords.c_readonlyTag));
keysNotInXml.Remove(itemKey);
}
// Remove items that are not present in the lua table
foreach (object key in keysNotInXml)
uafDictionnary.RemoveKey(key);
}
else if (collection is IUAFList)
{
IUAFList uafList = collection as IUAFList;
Type itemType = collection.ElementType;
bool isBasicType = BasicTypeList.IsBasicType(itemType);
int index = 0;
foreach (XmlNode itemNode in node.ChildNodes)
{
if (!itemNode.Name.Equals(KeyWords.c_itemTag, StringComparison.OrdinalIgnoreCase))
continue;
object uafListItem = null;
if (index < uafList.Count)
uafListItem = uafList.GetAt(index);
bool itemAlreadyInList = (uafListItem != null);
// For basic types, we have a "VAL" child node.
XmlNode valueNode = isBasicType ? itemNode.GetChildNodeByName(KeyWords.c_valTag) : itemNode;
if (valueNode == null)
throw new Exception("VerboseXmlSerializer.FillCollectionFromXmlNode: items of list" + collectionName + " have no \"VAL\" but they should have one.");
XmlNode innerXmlNode;
Type xmlType = GetTypeFromObjectXmlNode(valueNode, itemType, out innerXmlNode);
if (uafListItem != null && uafListItem.GetType() != xmlType)
uafListItem = null;
if (uafListItem != null && innerXmlNode != null)
{
FillObjectFromXmlNode(uafListItem, innerXmlNode);
}
else
{
uafListItem = (innerXmlNode != null ?
CreateObjectFromXmlNode(innerXmlNode, xmlType) :
Activator.CreateInstance(itemType));
if (itemAlreadyInList)
uafList.SetAt(index, uafListItem); // Update item
else
uafList.Add(uafListItem); // Add new item
}
if (innerXmlNode != null)
//uafList.SetPropertyMetaData(index, PropertiesMetaDataName.ReadOnly, innerXmlNode.GetBoolAttribute(KeyWords.c_readonlyTag));
uafList.SetPropertyMetaData(index, "readonly", innerXmlNode.GetBoolAttribute(KeyWords.c_readonlyTag));
++index;
}
// Remove items that are not present in the lua table
while (index < uafList.Count)
{
uafList.RemoveAt(index);
++index;
}
}
else
{
throw new Exception("LuaSerializer.FillCollectionFromLuaTable: Unknown collection type.");
}
}
protected bool BuildXmlDocumentFromObject(object obj)
{
m_doc = new XmlDocument();
XmlNode node = m_doc.CreateElement("root");
XmlDeclaration xmlDeclaration = m_doc.CreateXmlDeclaration("1.0", "ISO-8859-1", null);
m_doc.InsertBefore(xmlDeclaration, m_doc.DocumentElement);
m_doc.AppendChild(node);
try
{
AppendObjectToXml(obj, node);
return true;
}
catch (Exception)
{
return false;
}
}
protected override bool SaveObjectToFileImplementation(object obj, string file)
{
if (BuildXmlDocumentFromObject(obj))
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = "\t";
settings.OmitXmlDeclaration = false;
settings.Encoding = System.Text.Encoding.GetEncoding("ISO-8859-1");
using (XmlWriter writer = XmlTextWriter.Create(file, settings))
{
System.Xml.XPath.XPathNavigator navigator = m_doc.CreateNavigator();
writer.WriteNode(navigator, true);
}
return true;
}
return false;
}
protected void AddXmlAttribute(XmlNode xmlNode, string attributeName, string attributeValue)
{
XmlAttribute valueAttribute = m_doc.CreateAttribute(attributeName);
valueAttribute.Value = attributeValue;
xmlNode.Attributes.Append(valueAttribute);
}
protected XmlNode AddChildNode(XmlNode parentNode, string childName)
{
XmlNode childNode = m_doc.CreateElement(childName);
parentNode.AppendChild(childNode);
return childNode;
}
protected void AppendObjectToXml(object obj, XmlNode parentNode)
{
if (obj == null)
return;
XmlNode childNode = m_doc.CreateElement(obj.GetType().Name);
parentNode.AppendChild(childNode);
FillXmlFromObject(obj, childNode);
}
protected void AppendBasicTypeXml(string propertyName, object propertyValue, XmlNode parentNode)
{
string itemNodeValue = ConvertBaseTypeToString(propertyValue);
XmlNode valueNode = m_doc.CreateElement(propertyName);
parentNode.AppendChild(valueNode);
AddXmlAttribute(valueNode, KeyWords.c_valueTag, itemNodeValue);
}
protected void FillXmlFromObject(object obj, XmlNode node)
{
if (obj == null)
return;
Type objType = obj.GetType();
PropertyInfo[] fields = objType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo property in fields)
{
string propertyName = FormatPropertyName(property);
if (!ShouldSerialize(property))
continue;
Type propertyType = property.PropertyType;
object propertyValue = property.GetValue(obj, null);
if (propertyValue == null)
continue;
if (!ShouldSerialize(property, propertyValue))
continue;
if (BasicTypeList.IsBasicType(propertyType))
{
AppendBasicTypeXml(propertyName, propertyValue, node);
}
else if (IsCollection(propertyType))
{
var collection = (IUAFCollection)propertyValue;
Type itemType = collection.ElementType;
bool basicItemType = BasicTypeList.IsBasicType(itemType);
XmlNode childNode = AddChildNode(node, propertyName);
AddXmlAttribute(childNode, KeyWords.c_countTag, collection.Count.ToString());
foreach (object item in collection.SerializableElements)
{
XmlNode itemNode = AddChildNode(childNode, KeyWords.c_itemTag);
ISerializableKeyValuePair serializableItem = item as ISerializableKeyValuePair;
if (serializableItem != null)
{
AddXmlAttribute(itemNode, KeyWords.c_typeTag, serializableItem.Value.ToString());
}
if (basicItemType)
{
AppendBasicTypeXml(KeyWords.c_valTag, item, itemNode);
}
else
{
// If the collection is a dictionnary, we know that the items are a
// pair of some kind. We don't want the pair element to be
// written in the XML, we only want the KEY and the VAL elements so
// we use FillXmlFromObject
if (collection is IUAFDictionary)
{
FillXmlFromObject(item, itemNode);
}
else
{
AddXmlAttribute(itemNode, KeyWords.c_typeTag, item.GetType().Name);
AppendObjectToXml(item, itemNode);
}
}
}
}
else
{
XmlNode childNode = AddChildNode(node, propertyName);
AddXmlAttribute(childNode, KeyWords.c_typeTag, propertyValue.GetType().Name);
AppendObjectToXml(propertyValue, childNode);
}
}
}
private string ConvertBaseTypeToString(object obj)
{
if (obj.GetType().IsEnum)
{
return (Convert.ToInt32(obj)).ToString();
}
if (obj is Vec2d)
{
Vec2d vec = (Vec2d)obj;
return $"{vec.X} {vec.Y}".Replace(',', '.');
}
if (obj is Vec3d)
{
Vec3d vec = (Vec3d)obj;
return $"{vec.X} {vec.Y} {vec.Z}".Replace(',', '.');
}
if (obj is Color)
{
Color col = (Color)obj;
return $"0x{col.A:X2}{col.R:X2}{col.G:X2}{col.B:X2}";
}
if (obj is Angle)
{
return $"{((Angle)obj).ToDegree()}";
}
if (obj is bool)
{
return (bool)obj ? "1" : "0";
}
if (obj is char)
{
if ((char)obj == '\0')
return "";
return obj.ToString();
}
if (obj is float || obj is double || obj is decimal)
{
return obj.ToString().Replace(",", ".");
}
return obj.ToString();
}
/// <summary>
/// Checking if the property ParentBind is null because root actors can't have parentBind
/// </summary>
/// <param name="propertyInfo"></param>
/// <param name="propertyValue"></param>
/// <returns></returns>
public bool ShouldSerialize(PropertyInfo propertyInfo, object propertyValue)
{
return propertyInfo.Name != nameof(Actor.ParentBind) || !(propertyValue is Bind bind) || bind.ParentActor != "";
}
}
}