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