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

490 lines
18 KiB
C#

using System;
using System.Globalization;
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;
namespace JD.Serialization
{
public class XmlSerializer : Serializer
{
private XmlDocument m_doc;
public XmlSerializer()
: this(new NullTypeMap())
{
}
public XmlSerializer(ITypeMap alloc)
: base(alloc)
{
}
protected override object LoadObjectFromFileImplementation(string file, Type type)
{
using (var stream = File.OpenRead(file))
{
return Deserialize(stream);
}
}
protected override bool SaveObjectToFileImplementation(object obj, string file)
{
using (var stream = File.Create(file))
{
Serialize(obj, stream);
}
return false;
}
public void Serialize(object obj, Stream output)
{
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);
FillXmlFromObjectMain(obj, node);
//m_doc.Save(file);
System.Xml.XPath.XPathNavigator navigator = m_doc.CreateNavigator();
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(output, settings))
{
writer.WriteNode(navigator, true);
}
}
public object Deserialize(Stream stream)
{
m_doc = new XmlDocument();
m_doc.Load(stream);
XmlNode node = m_doc["root"];
if (node != null)
return CreateObjectFromXmlNode(node);
return null;
}
public object Deserialize(string xml)
{
m_doc = new XmlDocument();
m_doc.LoadXml(xml);
XmlNode node = m_doc["root"];
if (node != null)
return CreateObjectFromXmlNode(node);
return null;
}
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 t)
{
if (t.IsEnum)
{
return int.Parse(val);
}
object obj = null;
TypeConverter conv = TypeDescriptor.GetConverter(t);
switch (t.Name)
{
case "Boolean":
// Parse a long to bypass int overflow cases due to engine bbool being either 0, 1 or 4294967295 (-1)
return long.Parse(val) == 1;
case "int":
case "Int32":
return int.Parse(val);
case "uint":
case "UInt32":
return BringInRange(val);
case "float":
case "Single":
return float.Parse(val, CultureInfo.InvariantCulture);
case "double":
return double.Parse(val, CultureInfo.InvariantCulture);
case "ObjectPath":
return new ObjectPath(val);
case "String8":
case "String":
case "Platform":
obj = val;
break;
case "Path":
obj = val;
break;
case "ColorInteger":
case "StringID":
{
uint num;
if (uint.TryParse(val, out num))
obj = num;
else
obj = val;
}
break;
case "LocalisationId":
{
uint id = uint.Parse(val);
if (id == uint.MaxValue)
obj = -1;
else
obj = (int)id;
}
break;
case "Color":
var r = ColorF32ToU8(ExtractFloatFromString(val, 0));
var g = ColorF32ToU8(ExtractFloatFromString(val, 1));
var b = ColorF32ToU8(ExtractFloatFromString(val, 2));
var a = ColorF32ToU8(ExtractFloatFromString(val, 3));
return Color.FromArgb(a, r, g, b);
case "Angle":
case "Volume":
obj = float.Parse(val, CultureInfo.InvariantCulture);
break;
case "Vec2d":
{
float x = ExtractFloatFromString(val, 0);
float y = ExtractFloatFromString(val, 1);
return new Vec2d(x, y);
}
case "Vec3d":
{
float x = ExtractFloatFromString(val, 0);
float y = ExtractFloatFromString(val, 1);
float z = ExtractFloatFromString(val, 2);
return new Vec3d(x, y, z);
}
}
if (obj == null)
return null;
if (conv.CanConvertFrom(obj.GetType()))
{
return conv.ConvertFrom(obj);
}
return Convert.ChangeType(obj, t);
}
/// <summary>
/// Some parameters in the xml files, which are serialized as unsigned int, have negative values.
/// C++ Engine can handle these negative unsigned integers easily by rounding them off, but c# cannot.
/// This function replicates the c++ behaviour for negative unsigned intergers.
/// </summary>
/// <param name="val"></param>
/// <returns></returns>
private uint BringInRange(string val)
{
if (val.StartsWith("-"))
{
var interger = int.Parse(val) * -1;
var unsignedInteger = uint.MaxValue - (uint)interger + 1;
return unsignedInteger;
}
return uint.Parse(val);
}
public object CreateObjectFromXmlNode(XmlNode node)
{
// node are the parent one !!
XmlNode nodeObj = node.FirstChild;
Type objType = Type.GetType("JD.Serialization." + nodeObj.Name);
if (objType == null)
{
// Try to resolve as a child type
objType = GetTypeFromName(nodeObj.Name);
if (objType == null)
throw new Exception("Cannot find generated class named " + nodeObj.Name);
}
object obj = Activator.CreateInstance(TypeMap.ToRuntimeType(objType));
FillObjectFromXmlNode(obj, nodeObj);
return obj;
}
private static List<Type> _allSerializable;
private Type GetTypeFromName(string name)
{
if (_allSerializable == null)
{
_allSerializable = GetType().Assembly.GetTypes().Where(x => (x.Namespace ?? "").StartsWith("JD.Serialization")).ToList();
}
return _allSerializable.FirstOrDefault(x => x.Name == name);
}
private void FillObjectFromXmlNode(object obj, XmlNode node)
{
Type objType = obj.GetType();
PropertyInfo[] fields = objType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo property in fields)
{
string propName = FormatPropertyName(property);
if (!ShouldSerialize(property))
{
continue;
}
XmlNode nodeIn = null;
if (BasicTypeList.IsBasicType(property.PropertyType))
{
if (property.PropertyType.IsEnum)
{
nodeIn = node.Attributes["ENUM"];
if (nodeIn != null)
{
XmlAttribute selAttr = nodeIn.Attributes["SEL"];
if (selAttr != null)
property.SetValue(obj, int.Parse(selAttr.Value), null);
}
}
else
{
nodeIn = node.Attributes.Cast<XmlAttribute>().FirstOrDefault(x => x.Name.Equals(propName, StringComparison.OrdinalIgnoreCase));
if (nodeIn == null)
continue;
var value = CreateBaseTypeObject(nodeIn.Value, property.PropertyType);
property.SetValue(obj, value, null);
}
continue;
}
foreach (XmlNode childNode in node.ChildNodes)
{
if (childNode.Name.ToLower() == propName.ToLower())
{
nodeIn = childNode;
break;
}
}
if (nodeIn == null)
continue;
// enum special case:
if (property.PropertyType.IsGenericType)
{
if (property.PropertyType.GetGenericTypeDefinition() == typeof(UAFList<>))
{
Type t = property.PropertyType.GetGenericArguments()[0];
IList lst = CreateNewList(t);
if (BasicTypeList.IsBasicType(t))
{
foreach (XmlNode val in node.ChildNodes)
{
if (val.Name.ToLower() == propName.ToLower())
lst.Add(CreateBaseTypeObject(val.Attributes["VAL"].Value, t));
}
}
else
{
foreach (XmlNode val in node.ChildNodes)
{
if (val.Name.ToLower() == propName.ToLower())
{
var deserialized = CreateObjectFromXmlNode(val);
lst.Add(deserialized);
}
}
}
property.SetValue(obj, lst, null);
}
else if (property.PropertyType.GetGenericTypeDefinition() == typeof(UAFDictionary<,>))
{
if (nodeIn.ChildNodes.Count > 0)
{
throw new NotImplementedException("UAFDictionary are not supported yet by the serializer.");
}
}
else
{
property.SetValue(obj, CreateObjectFromXmlNode(nodeIn), null);
}
}
else
{
property.SetValue(obj, CreateObjectFromXmlNode(nodeIn), null);
}
}
}
public void FillXmlFromObjectMain(object obj, XmlNode node, bool setTypeName = false)
{
if (obj == null)
return;
XmlNode nodeIn = m_doc.CreateElement(obj.GetType().Name);
if (setTypeName)
{
XmlAttribute nodeAttribute = m_doc.CreateAttribute("NAME");
nodeAttribute.Value = obj.GetType().Name;
node.Attributes.Append(nodeAttribute);
}
FillXmlFromObject(obj, nodeIn);
node.AppendChild(nodeIn);
}
public 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 propName = FormatPropertyName(property);
if (!ShouldSerialize(property) || (property.IsOptional() && property.GetValue(obj) == null))
{
continue;
}
if (BasicTypeList.IsBasicType(property.PropertyType))
{
if (property.PropertyType.IsEnum)
{
XmlElement elmt = m_doc.CreateElement("ENUM");
XmlAttribute nameAttr = m_doc.CreateAttribute("NAME");
nameAttr.Value = propName;
XmlAttribute selAttr = m_doc.CreateAttribute("SEL");
selAttr.Value = (Convert.ToInt32(property.GetValue(obj, null))).ToString();
elmt.Attributes.Append(nameAttr);
elmt.Attributes.Append(selAttr);
node.AppendChild(elmt);
}
else
{
XmlAttribute nodeAttribute = m_doc.CreateAttribute(propName);
nodeAttribute.Value = ConvertBaseTypeToString(property.GetValue(obj, null));
node.Attributes.Append(nodeAttribute);
}
continue;
}
if (property.PropertyType.IsGenericType)
{
if (property.PropertyType.GetGenericTypeDefinition() == typeof(UAFList<>))
{
IList lst = (IList)(property.GetValue(obj, null));
foreach (object o in lst)
{
XmlNode elmt = m_doc.CreateElement(propName);
if (BasicTypeList.IsBasicType(o.GetType()))
{
XmlAttribute valAttr = m_doc.CreateAttribute("VAL");
valAttr.Value = ConvertBaseTypeToString(o);
elmt.Attributes.Append(valAttr);
}
else
{
FillXmlFromObjectMain(o, elmt, property.IsFactoryType(o.GetType()));
}
node.AppendChild(elmt);
}
}
else
{
XmlNode nodeIn = m_doc.CreateElement(propName);
FillXmlFromObjectMain(property.GetValue(obj, null), nodeIn);
node.AppendChild(nodeIn);
}
}
else
{
XmlNode nodeIn = m_doc.CreateElement(propName);
FillXmlFromObjectMain(property.GetValue(obj, null), nodeIn);
node.AppendChild(nodeIn);
}
}
}
private string ConvertBaseTypeToString(object obj)
{
if (obj == null)
{
return "";
}
if (obj.GetType().IsEnum)
{
return Convert.ToInt32(obj).ToString(CultureInfo.InvariantCulture);
}
switch (obj.GetType().Name)
{
case "Vec2d":
return string.Format(CultureInfo.InvariantCulture, "{0} {1}", ((Vec2d)obj).X, ((Vec2d)obj).Y);
case "Vec3d":
return string.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", ((Vec3d)obj).X, ((Vec3d)obj).Y, ((Vec3d)obj).Z);
case "Color":
return string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}"
, ((float)((Color)obj).R) * (1.0f / 255.0f)
, ((float)((Color)obj).G) * (1.0f / 255.0f)
, ((float)((Color)obj).B) * (1.0f / 255.0f)
, ((float)((Color)obj).A) * (1.0f / 255.0f));
case "Angle":
return string.Format(CultureInfo.InvariantCulture, "{0}", ((Angle)obj).ToDegree());
case "Boolean":
if ((bool)obj)
return "1";
else
return "0";
}
var formattable = obj as IFormattable;
if (formattable != null)
{
return formattable.ToString(string.Empty, CultureInfo.InvariantCulture);
}
return obj.ToString();
}
}
}