From af88bb2161e9e1bcad851714698eacf291030ba2 Mon Sep 17 00:00:00 2001 From: Flamefire Date: Mon, 27 Oct 2014 19:44:23 +0100 Subject: [PATCH] Serialize properties --- VocaluxeLib/Xml/CTypeExtensions.cs | 201 ++++++++++++++++++----------- VocaluxeLib/Xml/CXmlSerializer.cs | 18 +-- VocaluxeTests/XmlSerializerTest.cs | 31 +++++ 3 files changed, 163 insertions(+), 87 deletions(-) diff --git a/VocaluxeLib/Xml/CTypeExtensions.cs b/VocaluxeLib/Xml/CTypeExtensions.cs index a2427c52..51575ce4 100644 --- a/VocaluxeLib/Xml/CTypeExtensions.cs +++ b/VocaluxeLib/Xml/CTypeExtensions.cs @@ -40,9 +40,15 @@ namespace VocaluxeLib.Xml public struct SFieldInfo { + private readonly FieldInfo _Field; + private readonly PropertyInfo _Property; + + public Type Type + { + get { return _Field != null ? _Field.FieldType : _Property.PropertyType; } + } public string Name; public string AltName; - public FieldInfo Info; public object DefaultValue; public bool HasDefaultValue; public bool IsAttribute; @@ -54,6 +60,31 @@ namespace VocaluxeLib.Xml public XmlRangedAttribute Ranged; public Type SubType; public string ArrayItemName; + + public SFieldInfo(FieldInfo field) + : this() + { + _Field = field; + } + + public SFieldInfo(PropertyInfo property) + : this() + { + _Property = property; + } + + public void SetValue(object o, object value) + { + if (_Field != null) + _Field.SetValue(o, value); + else + _Property.SetValue(o, value, new object[] {}); + } + + public object GetValue(object o) + { + return (_Field != null) ?_Field.GetValue(o) :_Property.GetValue(o, new object[] {}); + } } public static class CTypeExtensions @@ -61,6 +92,86 @@ namespace VocaluxeLib.Xml private static readonly Dictionary> _CacheFields = new Dictionary>(); private static readonly Dictionary _CacheTypeName = new Dictionary(); + private static void _FillInfo(ref SFieldInfo info, MemberInfo field) + { + XmlAttributeAttribute attribute = field.GetAttribute(); + if (attribute != null) + { + info.IsAttribute = true; + info.Name = attribute.AttributeName; + } + else + { + XmlElementAttribute element = field.GetAttribute(); + if (element != null) + info.Name = element.ElementName; + else + { + XmlArrayAttribute array = field.GetAttribute(); + if (array != null) + { + Debug.Assert(info.Type.IsList() || info.Type.IsArray, "Only lists and arrays can have the array attribute"); + Debug.Assert(!info.IsAttribute, "Lists cannot be attributes"); + info.Name = array.ElementName; + info.IsList = true; + } + } + } + if (string.IsNullOrEmpty(info.Name)) + info.Name = field.Name; + XmlAltNameAttribute altName = field.GetAttribute(); + if (altName != null) + info.AltName = altName.AltName; + + if (info.Type.IsGenericType) + info.SubType = info.Type.GetGenericArguments().Last(); + if (info.Type.IsArray) + info.SubType = info.Type.GetElementType(); + if (info.Type.IsList() || info.Type.IsArray) + { + if (!info.IsList) + { + Debug.Assert(!field.HasAttribute(), "A field cannot have an XmlElement- and XmlArray-Attribute"); + info.IsEmbeddedList = true; + } + else + { + XmlArrayItemAttribute arrayItem = field.GetAttribute(); + if (arrayItem != null && !string.IsNullOrEmpty(arrayItem.ElementName)) + info.ArrayItemName = arrayItem.ElementName; + } + } + else if (info.Type.IsNullable()) + info.IsNullable = true; + else if (info.Type.IsDictionary()) + { + Debug.Assert(info.Type.GetGenericArguments()[0] == typeof(string), "Keys of dictionaries must be strings"); + info.IsDictionary = true; + } + + if (field.HasAttribute()) + { + Debug.Assert(info.Type == typeof(float) || (info.IsNullable && info.SubType == typeof(float)), "Only floats can be normalized"); + info.IsNormalized = true; + } + info.Ranged = field.GetAttribute(); + Debug.Assert(info.Ranged == null || info.Type == typeof(int) || info.Type == typeof(float) || info.Type == typeof(double), + "Only ints,floats and double can be ranged"); + + DefaultValueAttribute defAttr = field.GetAttribute(); + if (defAttr != null) + { + Debug.Assert(!info.Type.IsList(), "Lists cannot have a default value"); + info.HasDefaultValue = true; + info.DefaultValue = defAttr.Value; + } + else if (info.IsNullable) + { + info.HasDefaultValue = true; + info.DefaultValue = null; + } + } + public static IEnumerable GetFieldInfos(this Type type) { List result; @@ -72,83 +183,17 @@ namespace VocaluxeLib.Xml { if (field.HasAttribute()) continue; - XmlAttributeAttribute attribute = field.GetAttribute(); - SFieldInfo info = new SFieldInfo {Info = field}; - if (attribute != null) - { - info.IsAttribute = true; - info.Name = attribute.AttributeName; - } - else - { - XmlElementAttribute element = field.GetAttribute(); - if (element != null) - info.Name = element.ElementName; - else - { - XmlArrayAttribute array = field.GetAttribute(); - if (array != null) - { - Debug.Assert(field.FieldType.IsList() || field.FieldType.IsArray, "Only lists and arrays can have the array attribute"); - Debug.Assert(!info.IsAttribute, "Lists cannot be attributes"); - info.Name = array.ElementName; - info.IsList = true; - } - } - } - if (string.IsNullOrEmpty(info.Name)) - info.Name = field.Name; - XmlAltNameAttribute altName = field.GetAttribute(); - if (altName != null) - info.AltName = altName.AltName; - - if (field.FieldType.IsGenericType) - info.SubType = field.FieldType.GetGenericArguments().Last(); - if (field.FieldType.IsArray) - info.SubType = field.FieldType.GetElementType(); - if (field.FieldType.IsList() || field.FieldType.IsArray) - { - if (!info.IsList) - { - Debug.Assert(!field.HasAttribute(), "A field cannot have an XmlElement- and XmlArray-Attribute"); - info.IsEmbeddedList = true; - } - else - { - XmlArrayItemAttribute arrayItem = field.GetAttribute(); - if (arrayItem != null && !string.IsNullOrEmpty(arrayItem.ElementName)) - info.ArrayItemName = arrayItem.ElementName; - } - } - else if (field.FieldType.IsNullable()) - info.IsNullable = true; - else if (field.FieldType.IsDictionary()) - { - Debug.Assert(field.FieldType.GetGenericArguments()[0] == typeof(string), "Keys of dictionaries must be strings"); - info.IsDictionary = true; - } - - if (field.HasAttribute()) - { - Debug.Assert(field.FieldType == typeof(float) || (info.IsNullable && info.SubType == typeof(float)), "Only floats can be normalized"); - info.IsNormalized = true; - } - info.Ranged = field.GetAttribute(); - Debug.Assert(info.Ranged == null || field.FieldType == typeof(int) || field.FieldType == typeof(float) || field.FieldType == typeof(double), - "Only ints,floats and double can be ranged"); - - DefaultValueAttribute defAttr = field.GetAttribute(); - if (defAttr != null) - { - Debug.Assert(!field.FieldType.IsList(), "Lists cannot have a default value"); - info.HasDefaultValue = true; - info.DefaultValue = defAttr.Value; - } - else if (info.IsNullable) - { - info.HasDefaultValue = true; - info.DefaultValue = null; - } + SFieldInfo info = new SFieldInfo(field); + _FillInfo(ref info, field); + result.Add(info); + } + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (PropertyInfo property in properties) + { + if (property.HasAttribute()) + continue; + SFieldInfo info = new SFieldInfo(property); + _FillInfo(ref info, property); result.Add(info); } _CacheFields.Add(type, result); diff --git a/VocaluxeLib/Xml/CXmlSerializer.cs b/VocaluxeLib/Xml/CXmlSerializer.cs index 6a0d3a46..07f09c91 100644 --- a/VocaluxeLib/Xml/CXmlSerializer.cs +++ b/VocaluxeLib/Xml/CXmlSerializer.cs @@ -189,7 +189,7 @@ namespace VocaluxeLib.Xml } if (!field.HasDefaultValue) return false; - field.Info.SetValue(result, field.DefaultValue); + field.SetValue(result, field.DefaultValue); return true; } @@ -215,7 +215,7 @@ namespace VocaluxeLib.Xml private static void _AddList(object result, SFieldInfo listField, ICollection list) { - listField.Info.SetValue(result, _CreateList(listField.Info.FieldType, list)); + listField.SetValue(result, _CreateList(listField.Type, list)); } private void _ReadChildNodes(XmlNode parent, ref object result, bool attributes) @@ -279,7 +279,7 @@ namespace VocaluxeLib.Xml } else { - object value = _GetValue(node, field.Info.FieldType, field.ArrayItemName); + object value = _GetValue(node, field.Type, field.ArrayItemName); if (field.IsNormalized) { if (field.IsNullable && !((float?)value).Value.IsInRange(0, 1) || @@ -289,11 +289,11 @@ namespace VocaluxeLib.Xml if (field.Ranged != null && value != null) { bool ok = false; - if (field.Info.FieldType == typeof(int)) + if (field.Type == typeof(int)) ok = ((int)value).IsInRange(field.Ranged.Min, field.Ranged.Max); - else if (field.Info.FieldType == typeof(float)) + else if (field.Type == typeof(float)) ok = ((float)value).IsInRange(field.Ranged.Min, field.Ranged.Max); - else if (field.Info.FieldType == typeof(double)) + else if (field.Type == typeof(double)) ok = ((double)value).IsInRange(field.Ranged.Min, field.Ranged.Max); else Debug.Assert(false, "Ranged attribute has invalid type"); @@ -303,7 +303,7 @@ namespace VocaluxeLib.Xml ")"); } } - field.Info.SetValue(result, value); + field.SetValue(result, value); curField++; } } @@ -386,7 +386,7 @@ namespace VocaluxeLib.Xml IEnumerable fields = o.GetType().GetFieldInfos(); foreach (SFieldInfo field in fields) { - object value = field.Info.GetValue(o); + object value = field.GetValue(o); if (!_WriteDefaults && field.HasDefaultValue && Equals(value, field.DefaultValue)) continue; if (field.IsEmbeddedList) @@ -396,7 +396,7 @@ namespace VocaluxeLib.Xml _WriteNode(writer, field.Name, field.SubType, subValue, field.IsAttribute); } else - _WriteNode(writer, field.Name, field.Info.FieldType, value, field.IsAttribute, field.ArrayItemName); + _WriteNode(writer, field.Name, field.Type, value, field.IsAttribute, field.ArrayItemName); } } #endregion diff --git a/VocaluxeTests/XmlSerializerTest.cs b/VocaluxeTests/XmlSerializerTest.cs index 756a521b..ff5e1267 100644 --- a/VocaluxeTests/XmlSerializerTest.cs +++ b/VocaluxeTests/XmlSerializerTest.cs @@ -56,6 +56,21 @@ namespace VocaluxeTests [XmlElement("Entry")] public SEntry[] Ints; public int J; } + + private struct SProperty + { + [XmlIgnore] public int Private; + // ReSharper disable UnusedMember.Local + public int Public + { + get { return Private - 1; } + set { Private = value + 1; } + } + // ReSharper restore UnusedMember.Local + // ReSharper disable UnusedAutoPropertyAccessor.Local + public int Auto { get; set; } + // ReSharper restore UnusedAutoPropertyAccessor.Local + } #pragma warning restore 169 #pragma warning restore 649 @@ -285,6 +300,22 @@ namespace VocaluxeTests _AssertFail(() => xml.DeserializeString(_Empty)); } + private const string _XmlProperty = _Head + @" + 2 + 3 +"; + + [TestMethod] + public void TestProperty() + { + var xml = new CXmlSerializer(); + SProperty foo = xml.DeserializeString(_XmlProperty); + Assert.AreEqual(3, foo.Private); + Assert.AreEqual(3, foo.Auto); + string newXml = xml.Serialize(foo); + Assert.AreEqual(_XmlProperty, newXml); + } + [TestMethod] public void TestRealFiles() {