FlashPatcher/FlashPatcher/TaskService/XmlSerializationHelper.cs

527 lines
17 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using JetBrains.Annotations;
namespace Microsoft.Win32.TaskScheduler
{
internal static class XmlSerializationHelper
{
public static object GetDefaultValue([NotNull] PropertyInfo prop)
{
var attributes = prop.GetCustomAttributes(typeof(DefaultValueAttribute), true);
if (attributes.Length > 0)
{
var defaultAttr = (DefaultValueAttribute)attributes[0];
return defaultAttr.Value;
}
// Attribute not found, fall back to default value for the type
if (prop.PropertyType.IsValueType)
return Activator.CreateInstance(prop.PropertyType);
return null;
}
private static bool GetPropertyValue(object obj, [NotNull] string property, ref object outVal)
{
PropertyInfo pi = obj?.GetType().GetProperty(property);
if (pi != null)
{
outVal = pi.GetValue(obj, null);
return true;
}
return false;
}
private static bool GetAttributeValue(Type objType, Type attrType, string property, bool inherit, ref object outVal)
{
object[] attrs = objType.GetCustomAttributes(attrType, inherit);
if (attrs.Length > 0)
return GetPropertyValue(attrs[0], property, ref outVal);
return false;
}
private static bool GetAttributeValue([NotNull] PropertyInfo propInfo, Type attrType, string property, bool inherit, ref object outVal)
{
Attribute attr = Attribute.GetCustomAttribute(propInfo, attrType, inherit);
return GetPropertyValue(attr, property, ref outVal);
}
private static bool IsStandardType(Type type) => type.IsPrimitive || type == typeof(DateTime) || type == typeof(DateTimeOffset) || type == typeof(Decimal) || type == typeof(Guid) || type == typeof(TimeSpan) || type == typeof(string) || type.IsEnum;
private static bool HasMembers([NotNull] object obj)
{
if (obj is IXmlSerializable)
{
using (System.IO.MemoryStream mem = new System.IO.MemoryStream())
{
using (XmlTextWriter tw = new XmlTextWriter(mem, Encoding.UTF8))
{
((IXmlSerializable)obj).WriteXml(tw);
tw.Flush();
return mem.Length > 3;
}
}
}
// Enumerate each public property
PropertyInfo[] props = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var pi in props)
{
if (!Attribute.IsDefined(pi, typeof(XmlIgnoreAttribute), false))
{
object value = pi.GetValue(obj, null);
if (!Equals(value, GetDefaultValue(pi)))
{
if (!IsStandardType(pi.PropertyType))
{
if (HasMembers(value))
return true;
}
else
return true;
}
}
}
return false;
}
public static string GetPropertyAttributeName([NotNull] PropertyInfo pi)
{
object oVal = null;
string eName = pi.Name;
if (GetAttributeValue(pi, typeof(XmlAttributeAttribute), "AttributeName", false, ref oVal))
eName = oVal.ToString();
return eName;
}
public static string GetPropertyElementName([NotNull] PropertyInfo pi)
{
object oVal = null;
string eName = pi.Name;
if (GetAttributeValue(pi, typeof(XmlElementAttribute), "ElementName", false, ref oVal))
eName = oVal.ToString();
else if (GetAttributeValue(pi.PropertyType, typeof(XmlRootAttribute), "ElementName", true, ref oVal))
eName = oVal.ToString();
return eName;
}
public delegate bool PropertyConversionHandler([NotNull] PropertyInfo pi, Object obj, ref Object value);
public static bool WriteProperty([NotNull] XmlWriter writer, [NotNull] PropertyInfo pi, [NotNull] Object obj, PropertyConversionHandler handler = null)
{
if (Attribute.IsDefined(pi, typeof(XmlIgnoreAttribute), false) || Attribute.IsDefined(pi, typeof(XmlAttributeAttribute), false))
return false;
object value = pi.GetValue(obj, null);
object defValue = GetDefaultValue(pi);
if ((value == null && defValue == null) || (value != null && value.Equals(defValue)))
return false;
Type propType = pi.PropertyType;
if (handler != null && handler(pi, obj, ref value))
propType = value.GetType();
bool isStdType = IsStandardType(propType);
bool rw = pi.CanRead && pi.CanWrite;
bool ro = pi.CanRead && !pi.CanWrite;
string eName = GetPropertyElementName(pi);
if (isStdType && rw)
{
string output = GetXmlValue(value, propType);
if (output != null)
writer.WriteElementString(eName, output);
}
else if (!isStdType)
{
object outVal = null;
if (propType.GetInterface("IXmlSerializable") == null && GetAttributeValue(pi, typeof(XmlArrayAttribute), "ElementName", true, ref outVal) && propType.GetInterface("IEnumerable") != null)
{
if (string.IsNullOrEmpty(outVal.ToString())) outVal = eName;
writer.WriteStartElement(outVal.ToString());
var attributes = Attribute.GetCustomAttributes(pi, typeof(XmlArrayItemAttribute), true);
var dict = new Dictionary<Type, string>(attributes.Length);
foreach (XmlArrayItemAttribute a in attributes)
dict.Add(a.Type, a.ElementName);
foreach (object item in ((System.Collections.IEnumerable)value))
{
string aeName;
Type itemType = item.GetType();
if (dict.TryGetValue(itemType, out aeName))
{
if (IsStandardType(itemType))
writer.WriteElementString(aeName, GetXmlValue(item, itemType));
else
WriteObject(writer, item, null, false, aeName);
}
}
writer.WriteEndElement();
}
else
WriteObject(writer, value);
}
return false;
}
private static string GetXmlValue([NotNull] object value, Type propType)
{
string output = null;
if (propType.IsEnum)
{
if (Attribute.IsDefined(propType, typeof(FlagsAttribute), false))
output = Convert.ChangeType(value, Enum.GetUnderlyingType(propType)).ToString();
else
output = value.ToString();
}
else
{
switch (propType.FullName)
{
case "System.Boolean":
output = XmlConvert.ToString((System.Boolean)value);
break;
case "System.Byte":
output = XmlConvert.ToString((System.Byte)value);
break;
case "System.Char":
output = XmlConvert.ToString((System.Char)value);
break;
case "System.DateTime":
output = XmlConvert.ToString((System.DateTime)value, XmlDateTimeSerializationMode.RoundtripKind);
break;
case "System.DateTimeOffset":
output = XmlConvert.ToString((System.DateTimeOffset)value);
break;
case "System.Decimal":
output = XmlConvert.ToString((System.Decimal)value);
break;
case "System.Double":
output = XmlConvert.ToString((System.Double)value);
break;
case "System.Single":
output = XmlConvert.ToString((System.Single)value);
break;
case "System.Guid":
output = XmlConvert.ToString((System.Guid)value);
break;
case "System.Int16":
output = XmlConvert.ToString((System.Int16)value);
break;
case "System.Int32":
output = XmlConvert.ToString((System.Int32)value);
break;
case "System.Int64":
output = XmlConvert.ToString((System.Int64)value);
break;
case "System.SByte":
output = XmlConvert.ToString((System.SByte)value);
break;
case "System.TimeSpan":
output = XmlConvert.ToString((System.TimeSpan)value);
break;
case "System.UInt16":
output = XmlConvert.ToString((System.UInt16)value);
break;
case "System.UInt32":
output = XmlConvert.ToString((System.UInt32)value);
break;
case "System.UInt64":
output = XmlConvert.ToString((System.UInt64)value);
break;
default:
output = value == null ? string.Empty : value.ToString();
break;
}
}
return output;
}
public static void WriteObjectAttributes([NotNull] XmlWriter writer, [NotNull] object obj, PropertyConversionHandler handler = null)
{
// Enumerate each property
foreach (var pi in obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
if (Attribute.IsDefined(pi, typeof(XmlAttributeAttribute), false))
WriteObjectAttribute(writer, pi, obj, handler);
}
public static void WriteObjectAttribute([NotNull] XmlWriter writer, [NotNull] PropertyInfo pi, [NotNull] object obj, PropertyConversionHandler handler = null)
{
object value = pi.GetValue(obj, null);
object defValue = GetDefaultValue(pi);
if ((value == null && defValue == null) || (value != null && value.Equals(defValue)))
return;
Type propType = pi.PropertyType;
if (handler != null && handler(pi, obj, ref value))
propType = value.GetType();
writer.WriteAttributeString(GetPropertyAttributeName(pi), GetXmlValue(value, propType));
}
public static void WriteObjectProperties([NotNull] XmlWriter writer, [NotNull] object obj, PropertyConversionHandler handler = null)
{
// Enumerate each public property
foreach (var pi in obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
WriteProperty(writer, pi, obj, handler);
}
public static void WriteObject([NotNull] XmlWriter writer, [NotNull] object obj, PropertyConversionHandler handler = null, bool includeNS = false, string elemName = null)
{
if (obj == null)
return;
// Get name of top level element
string oName = elemName ?? GetElementName(obj);
if (!HasMembers(obj))
return;
if (includeNS)
writer.WriteStartElement(oName, GetTopLevelNamespace(obj));
else
writer.WriteStartElement(oName);
if (obj is IXmlSerializable)
{
((IXmlSerializable)obj).WriteXml(writer);
}
else
{
WriteObjectAttributes(writer, obj, handler);
WriteObjectProperties(writer, obj, handler);
}
writer.WriteEndElement();
}
public static string GetElementName([NotNull] object obj)
{
object oVal = null;
return GetAttributeValue(obj.GetType(), typeof(XmlRootAttribute), "ElementName", true, ref oVal) ? oVal.ToString() : obj.GetType().Name;
}
public static string GetTopLevelNamespace([NotNull] object obj)
{
object oVal = null;
return GetAttributeValue(obj.GetType(), typeof(XmlRootAttribute), "Namespace", true, ref oVal) ? oVal.ToString() : null;
}
public static void ReadObjectProperties([NotNull] XmlReader reader, [NotNull] object obj, PropertyConversionHandler handler = null)
{
// Build property lookup table
PropertyInfo[] props = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
Dictionary<string, PropertyInfo> attrHash = new Dictionary<string, PropertyInfo>(props.Length);
Dictionary<string, PropertyInfo> propHash = new Dictionary<string, PropertyInfo>(props.Length);
foreach (var pi in props)
{
if (!Attribute.IsDefined(pi, typeof(XmlIgnoreAttribute), false))
{
if (Attribute.IsDefined(pi, typeof(XmlAttributeAttribute), false))
attrHash.Add(GetPropertyAttributeName(pi), pi);
else
propHash.Add(GetPropertyElementName(pi), pi);
}
}
if (reader.HasAttributes)
{
for (int i = 0; i < reader.AttributeCount; i++)
{
PropertyInfo pi;
reader.MoveToAttribute(i);
if (attrHash.TryGetValue(reader.LocalName, out pi))
{
if (IsStandardType(pi.PropertyType))
{
object value = null;
if (pi.PropertyType.IsEnum)
value = Enum.Parse(pi.PropertyType, reader.Value);
else
value = Convert.ChangeType(reader.Value, pi.PropertyType);
if (handler != null)
handler(pi, obj, ref value);
pi.SetValue(obj, value, null);
}
}
}
}
while (reader.MoveToContent() == XmlNodeType.Element)
{
PropertyInfo pi;
object outVal = null;
if (propHash.TryGetValue(reader.LocalName, out pi))
{
var tc = TypeDescriptor.GetConverter(pi.PropertyType);
if (IsStandardType(pi.PropertyType))
{
object value = null;
if (pi.PropertyType.IsEnum)
value = Enum.Parse(pi.PropertyType, reader.ReadElementContentAsString());
else if (pi.PropertyType == typeof(Guid))
value = new GuidConverter().ConvertFromString(reader.ReadElementContentAsString());
else
value = reader.ReadElementContentAs(pi.PropertyType, null);
if (handler != null)
handler(pi, obj, ref value);
pi.SetValue(obj, value, null);
}
else if (pi.PropertyType == typeof(Version))
{
Version v = new Version(reader.ReadElementContentAsString());
pi.SetValue(obj, v, null);
}
else if (pi.PropertyType.GetInterface("IEnumerable") != null && pi.PropertyType.GetInterface("IXmlSerializable") == null && GetAttributeValue(pi, typeof(XmlArrayAttribute), "ElementName", true, ref outVal))
{
string elem = string.IsNullOrEmpty(outVal?.ToString()) ? pi.Name : outVal.ToString();
reader.ReadStartElement(elem);
var attributes = Attribute.GetCustomAttributes(pi, typeof(XmlArrayItemAttribute), true);
var dict = new Dictionary<string, Type>(attributes.Length);
foreach (XmlArrayItemAttribute a in attributes)
dict.Add(a.ElementName, a.Type);
List<object> output = new List<object>();
while (reader.MoveToContent() == XmlNodeType.Element)
{
Type itemType;
if (dict.TryGetValue(reader.LocalName, out itemType))
{
object o;
if (IsStandardType(itemType))
o = reader.ReadElementContentAs(itemType, null);
else
{
o = Activator.CreateInstance(itemType);
ReadObject(reader, o, handler);
}
if (o != null)
output.Add(o);
}
}
reader.ReadEndElement();
if (output.Count > 0)
{
System.Collections.IEnumerable par = output;
Type et = typeof(object);
if (dict.Count == 1)
{
foreach (var v in dict.Values) { et = v; break; }
}
/*else
{
Type t1 = output[0].GetType();
bool same = true;
foreach (var item in output)
if (item.GetType() != t1) { same = false; break; }
if (same)
et = t1;
}
if (et != typeof(object))
{
Array ao = Array.CreateInstance(et, output.Count);
for (int i = 0; i < output.Count; i++)
ao.SetValue(output[i], i);
par = ao;
}
else
par = output.ToArray();*/
bool done = false;
if (pi.PropertyType == par.GetType() || (pi.PropertyType.IsArray && (pi.PropertyType.GetElementType() == typeof(object) || pi.PropertyType.GetElementType() == et)))
try { pi.SetValue(obj, par, null); done = true; } catch { }
if (!done)
{
var mi = pi.PropertyType.GetMethod("AddRange", new Type[] { typeof(System.Collections.IEnumerable) });
if (mi != null)
try { mi.Invoke(pi.GetValue(obj, null), new object[] { par }); done = true; } catch { }
}
if (!done)
{
var mi = pi.PropertyType.GetMethod("Add", new Type[] { typeof(object) });
if (mi != null)
try { foreach (var i in par) mi.Invoke(pi.GetValue(obj, null), new object[] { i }); done = true; } catch { }
}
if (!done && et != typeof(Object))
{
var mi = pi.PropertyType.GetMethod("Add", new Type[] { et });
if (mi != null)
try { foreach (var i in par) mi.Invoke(pi.GetValue(obj, null), new object[] { i }); done = true; } catch { }
}
// Throw error if not done
}
}
else
{
object inst = pi.GetValue(obj, null) ?? Activator.CreateInstance(pi.PropertyType);
if (inst == null)
throw new InvalidOperationException($"Can't get instance of {pi.PropertyType.Name}.");
ReadObject(reader, inst, handler);
}
}
else
{
reader.Skip();
reader.MoveToContent();
}
}
}
public static void ReadObject([NotNull] XmlReader reader, [NotNull] object obj, PropertyConversionHandler handler = null)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
reader.MoveToContent();
if (obj is IXmlSerializable)
{
((IXmlSerializable)obj).ReadXml(reader);
}
else
{
object oVal = null;
string oName = GetAttributeValue(obj.GetType(), typeof(XmlRootAttribute), "ElementName", true, ref oVal) ? oVal.ToString() : obj.GetType().Name;
if (reader.LocalName != oName)
throw new XmlException("XML element name does not match object.");
if (!reader.IsEmptyElement)
{
reader.ReadStartElement();
reader.MoveToContent();
ReadObjectProperties(reader, obj, handler);
reader.ReadEndElement();
}
else
reader.Skip();
}
}
public static void ReadObjectFromXmlText([NotNull] string xml, [NotNull] object obj, PropertyConversionHandler handler = null)
{
using (System.IO.StringReader sr = new System.IO.StringReader(xml))
{
using (XmlReader reader = XmlReader.Create(sr))
{
reader.MoveToContent();
ReadObject(reader, obj, handler);
}
}
}
public static string WriteObjectToXmlText([NotNull] object obj, PropertyConversionHandler handler = null)
{
StringBuilder sb = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(sb, new XmlWriterSettings() { Indent = true }))
WriteObject(writer, obj, handler, true);
return sb.ToString();
}
}
}