862 lines
33 KiB
C#
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 != "";
|
|
}
|
|
|
|
}
|
|
}
|