diff --git a/VocaluxeLib/Xml/CTypeExtensions.cs b/VocaluxeLib/Xml/CTypeExtensions.cs index 51575ce4..30b45450 100644 --- a/VocaluxeLib/Xml/CTypeExtensions.cs +++ b/VocaluxeLib/Xml/CTypeExtensions.cs @@ -55,6 +55,7 @@ namespace VocaluxeLib.Xml public bool IsList; //List with child elements () public bool IsEmbeddedList; //List w/o child elements() public bool IsDictionary; //List where the element names are the keys + public bool IsByteArray; // Byte arrays w/o XmlArrayAttribute are serialized as Base64-Encoded strings public bool IsNullable; public bool IsNormalized; public XmlRangedAttribute Ranged; @@ -83,7 +84,7 @@ namespace VocaluxeLib.Xml public object GetValue(object o) { - return (_Field != null) ?_Field.GetValue(o) :_Property.GetValue(o, new object[] {}); + return (_Field != null) ? _Field.GetValue(o) : _Property.GetValue(o, new object[] {}); } } @@ -132,7 +133,10 @@ namespace VocaluxeLib.Xml if (!info.IsList) { Debug.Assert(!field.HasAttribute(), "A field cannot have an XmlElement- and XmlArray-Attribute"); - info.IsEmbeddedList = true; + if (info.Type.IsArray && info.SubType == typeof(byte)) + info.IsByteArray = true; + else + info.IsEmbeddedList = true; } else { @@ -181,7 +185,7 @@ namespace VocaluxeLib.Xml FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (FieldInfo field in fields) { - if (field.HasAttribute()) + if (field.HasAttribute() || field.Name.EndsWith("Specified")) continue; SFieldInfo info = new SFieldInfo(field); _FillInfo(ref info, field); @@ -190,7 +194,7 @@ namespace VocaluxeLib.Xml PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (PropertyInfo property in properties) { - if (property.HasAttribute()) + if (property.HasAttribute() || property.Name.EndsWith("Specified") || !property.CanRead || !property.CanWrite) continue; SFieldInfo info = new SFieldInfo(property); _FillInfo(ref info, property); diff --git a/VocaluxeLib/Xml/CXmlSerializer.cs b/VocaluxeLib/Xml/CXmlSerializer.cs index f78717be..24969635 100644 --- a/VocaluxeLib/Xml/CXmlSerializer.cs +++ b/VocaluxeLib/Xml/CXmlSerializer.cs @@ -227,55 +227,47 @@ namespace VocaluxeLib.Xml nodes = parent.ChildNodes; List fields = result.GetType().GetFields(attributes); - int curField = 0; if (nodes != null) { - List subValues = new List(); - SFieldInfo curListField = new SFieldInfo(); - string curListName = null; + //Dictionary of all embedded lists to allow interleaved/mixed elements + Dictionary>> embLists = new Dictionary>>(); foreach (XmlNode node in nodes) { if (node is XmlComment) continue; - if (curListName != null) - { - if (node.Name != curListName && !node.Name.StartsWith(curListField.Name)) - { - // End an embedded List - _AddList(result, curListField, subValues); - curField++; - curListName = null; - } - else - { - // Continue an embedded List - object subValue = _GetValue(node, curListField.SubType); - subValues.Add(subValue); - continue; - } - } SFieldInfo field = new SFieldInfo(); - for (; curField < fields.Count; curField++) + int curField; + for (curField = 0; curField < fields.Count; curField++) { field = fields[curField]; if (field.Name == node.Name || field.AltName == node.Name || (field.IsEmbeddedList && node.Name.StartsWith(field.Name))) break; - if (!_CheckAndSetDefaultValue(result, field)) - throw new XmlException("Unexpected element: " + _GetXPath(node) + "; Expected: " + field.Name); } if (curField >= fields.Count) - throw new XmlException("Unexpected element: " + _GetXPath(node)); - - if (field.IsEmbeddedList) { - // Start an embedded List - curListField = field; - curListName = field.Name; - subValues.Clear(); - object subValue = _GetValue(node, curListField.SubType); - subValues.Add(subValue); + string msg = "Unexpected element: " + _GetXPath(node); + if (fields.Count > 0) + msg += " Expected: " + string.Join(", ", fields.Select(f => f.Name)); + throw new XmlException(msg); + } + + if (field.IsByteArray) + { + field.SetValue(result, Convert.FromBase64String(node.InnerText)); + fields.RemoveAt(curField); + } + else if (field.IsEmbeddedList) + { + Tuple> entry; + if (!embLists.TryGetValue(field.Name, out entry)) + { + entry = new Tuple>(field, new List()); + embLists.Add(field.Name, entry); + } + object subValue = _GetValue(node, field.SubType); + entry.Item2.Add(subValue); } else { @@ -304,19 +296,18 @@ namespace VocaluxeLib.Xml } } field.SetValue(result, value); - curField++; + fields.RemoveAt(curField); } } - if (curListName != null) + //Add embedded lists + foreach (Tuple> entry in embLists.Values) { - // End an embedded List - _AddList(result, curListField, subValues); - curField++; + _AddList(result, entry.Item1, entry.Item2); + fields.Remove(entry.Item1); } } - for (; curField < fields.Count; curField++) + foreach (SFieldInfo field in fields) { - SFieldInfo field = fields[curField]; if (!_CheckAndSetDefaultValue(result, field)) throw new XmlException("element: " + field.Name + " is missing in " + _GetXPath(parent)); } @@ -395,6 +386,8 @@ namespace VocaluxeLib.Xml foreach (object subValue in values) _WriteNode(writer, field.Name, field.SubType, subValue, field.IsAttribute); } + else if (field.IsByteArray) + writer.WriteElementString(field.Name, Convert.ToBase64String((byte[])value)); else _WriteNode(writer, field.Name, field.Type, value, field.IsAttribute, field.ArrayItemName); } @@ -410,11 +403,23 @@ namespace VocaluxeLib.Xml xDoc.Load(reader); if (xDoc.DocumentElement == null) throw new XmlException("No root element found!"); - _ReadChildNodes(xDoc.DocumentElement, ref result, false); + try + { + _ReadChildNodes(xDoc.DocumentElement, ref result, false); + } + catch (TargetInvocationException e) + { + throw e.InnerException ?? e; + } return (T)result; } public T DeserializeString(string xml) where T : new() + { + return DeserializeString(xml, new T()); + } + + public T DeserializeString(string xml, T o) where T : new() { var reader = new XmlTextReader(new StringReader(xml)) { @@ -424,7 +429,7 @@ namespace VocaluxeLib.Xml }; try { - return _Deserialize(reader, new T()); + return _Deserialize(reader, o); } finally { @@ -432,11 +437,15 @@ namespace VocaluxeLib.Xml } } - public T Deserialize(string filePath, object o = null) where T : new() + public T Deserialize(string filePath) where T : new() { - Debug.Assert(o == null || o.GetType() is T); - if (o == null) - o = new T(); + return Deserialize(filePath, new T()); + } + + public T Deserialize(string filePath, T o) where T : new() + { + if (!File.Exists(filePath)) + throw new FileNotFoundException(filePath); var reader = new XmlTextReader(filePath) { WhitespaceHandling = WhitespaceHandling.Significant, diff --git a/VocaluxeTests/XmlSerializerTest.cs b/VocaluxeTests/XmlSerializerTest.cs index ff5e1267..696c1d8d 100644 --- a/VocaluxeTests/XmlSerializerTest.cs +++ b/VocaluxeTests/XmlSerializerTest.cs @@ -71,26 +71,26 @@ namespace VocaluxeTests public int Auto { get; set; } // ReSharper restore UnusedAutoPropertyAccessor.Local } + + private struct SBytes + { + public byte[] B; + } + + private struct SIgnore + { + [XmlIgnore] public int I; + public int J; + } + + private class CIgnore + { + [XmlIgnore] public int I; + public int J; + } #pragma warning restore 169 #pragma warning restore 649 - [TestMethod] - public void TestBasic() - { - const string s = @" - 1 - 2 - 3 - 4 -"; - var xml = new CXmlSerializer(); - SBasic foo = xml.DeserializeString(s); - Assert.AreEqual(1, foo.I); - Assert.AreEqual("2", foo.S); - Assert.AreEqual(3, foo.F, 0.0001); - Assert.AreEqual(4, foo.D, 0.0001); - } - private static void _AssertFail(Action test) where T : Exception { try @@ -104,6 +104,49 @@ namespace VocaluxeTests } } + private static void _AssertFail(String xmlString) where T2 : Exception where T : new() + { + var xml = new CXmlSerializer(); + _AssertFail(() => xml.DeserializeString(xmlString)); + } + + private static T _AssertSerDeserMatch(string xmlString) where T : new() + { + var xml = new CXmlSerializer(); + T foo = xml.DeserializeString(xmlString); + string xmlNew = xml.Serialize(foo); + Assert.AreEqual(xmlString, xmlNew); + return foo; + } + + [TestMethod] + public void TestBasic() + { + const string s = @" + 1 + 2 + 3 + 4 +"; + const string sUnordered = @" + 4 + 1 + 3 + 2 +"; + var xml = new CXmlSerializer(); + SBasic foo = xml.DeserializeString(s); + Assert.AreEqual(1, foo.I); + Assert.AreEqual("2", foo.S); + Assert.AreEqual(3, foo.F, 0.0001); + Assert.AreEqual(4, foo.D, 0.0001); + foo = xml.DeserializeString(sUnordered); + Assert.AreEqual(1, foo.I); + Assert.AreEqual("2", foo.S); + Assert.AreEqual(3, foo.F, 0.0001); + Assert.AreEqual(4, foo.D, 0.0001); + } + [TestMethod] public void TestMissingXmlElement() { @@ -120,11 +163,10 @@ namespace VocaluxeTests 2 3 "}; - var xml = new CXmlSerializer(); foreach (string s1 in s) { string sTmp = s1; - _AssertFail(() => xml.DeserializeString(sTmp)); + _AssertFail(sTmp); } } @@ -150,11 +192,10 @@ namespace VocaluxeTests 4 1 "}; - var xml = new CXmlSerializer(); foreach (string s1 in s) { string sTmp = s1; - _AssertFail(() => xml.DeserializeString(sTmp)); + _AssertFail(sTmp); } } @@ -167,10 +208,7 @@ namespace VocaluxeTests 3 4 "; - var xml = new CXmlSerializer(); - SBasic foo = xml.DeserializeString(s); - string s2 = xml.Serialize(foo); - Assert.AreEqual(s, s2); + _AssertSerDeserMatch(s); } private readonly string[] _XMLList = new string[] @@ -212,7 +250,7 @@ namespace VocaluxeTests Assert.AreEqual(foo.Ints.Count, 0, "Deserialization2 failed"); res = xml.Serialize(foo); Assert.AreEqual(_XMLList[2], res, "Serialization2 failed"); - _AssertFail(() => xml.DeserializeString(_Empty)); + _AssertFail(_Empty); } [TestMethod] @@ -233,7 +271,7 @@ namespace VocaluxeTests Assert.AreEqual(foo.Ints.Length, 0, "Deserialization2 failed"); res = xml.Serialize(foo); Assert.AreEqual(_XMLList[2], res, "Serialization2 failed"); - _AssertFail(() => xml.DeserializeString(_Empty)); + _AssertFail(_Empty); } private const string _XMLListEmb = _Head + @" @@ -276,7 +314,7 @@ namespace VocaluxeTests Assert.AreEqual(foo.Ints.Count, 0, "Deserialization failed"); res = xml.Serialize(foo); Assert.AreEqual(_XMLListEmb3, res, "Serialization failed"); - _AssertFail(() => xml.DeserializeString(_Empty)); + _AssertFail(_Empty); } [TestMethod] @@ -297,23 +335,62 @@ namespace VocaluxeTests Assert.AreEqual(foo.Ints.Length, 0, "Deserialization failed"); res = xml.Serialize(foo); Assert.AreEqual(_XMLListEmb3, res, "Serialization failed"); - _AssertFail(() => xml.DeserializeString(_Empty)); + _AssertFail(_Empty); } - private const string _XmlProperty = _Head + @" - 2 - 3 -"; - [TestMethod] public void TestProperty() { + const string xmlString = _Head + @" + 2 + 3 +"; var xml = new CXmlSerializer(); - SProperty foo = xml.DeserializeString(_XmlProperty); + SProperty foo = xml.DeserializeString(xmlString); Assert.AreEqual(3, foo.Private); Assert.AreEqual(3, foo.Auto); string newXml = xml.Serialize(foo); - Assert.AreEqual(_XmlProperty, newXml); + Assert.AreEqual(xmlString, newXml); + } + + [TestMethod] + public void TestByteArray() + { + const string xmlString = _Head + @" + MTMzNw== +"; + _AssertSerDeserMatch(xmlString); + } + + private const string _XmlIgnore = _Head + @" + 2 +"; + private const string _XmlIgnore2 = _Head + @" + 2 + 2 +"; + + [TestMethod] + public void TestIgnore() + { + _AssertSerDeserMatch(_XmlIgnore); + _AssertFail(_XmlIgnore2); + } + + [TestMethod] + public void TestExisting() + { + var foo = new SIgnore {I = 1}; + var xml = new CXmlSerializer(); + SIgnore bar = xml.DeserializeString(_XmlIgnore, foo); + Assert.AreEqual(1, bar.I); + Assert.AreEqual(_XmlIgnore, xml.Serialize(bar)); + + var foo2 = new CIgnore {I = 1}; + CIgnore bar2 = xml.DeserializeString(_XmlIgnore, foo2); + Assert.AreEqual(1, bar2.I); + Assert.AreEqual(foo2.J, bar2.J, "Original classes should be modified by the deserialization"); + Assert.AreEqual(_XmlIgnore, xml.Serialize(bar2)); } [TestMethod] @@ -325,7 +402,7 @@ namespace VocaluxeTests { string xmlPath = Path.Combine(filePath, type.Name + ".xml"); var xml = new CXmlSerializer(type == typeof(CConfig.SConfig)); - object foo = xml.GetType().GetMethod("Deserialize").MakeGenericMethod(new Type[] {type}).Invoke(xml, new object[] {xmlPath}); + object foo = xml.Deserialize(xmlPath, Activator.CreateInstance(type)); Assert.IsInstanceOfType(foo, type); string newXml = xml.Serialize(foo); string oldXml = File.ReadAllText(xmlPath);