490 lines
18 KiB
C#
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();
|
|
}
|
|
|
|
}
|
|
}
|