#region license // This file is part of Vocaluxe. // // Vocaluxe is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Vocaluxe is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Vocaluxe. If not, see . #endregion using System; using System.Collections; using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; using System.Xml; using System.Xml.Serialization; namespace VocaluxeLib.Xml { public delegate string GetCommentDelegate(string name); public class CXmlSerializer { private readonly GetCommentDelegate _GetCommentCallback; private readonly bool _WriteDefaults; /// /// Creates a new serializer /// /// When true, also nodes with default values will be written /// Callback that is called for each node and should return a string that will be used as a comment public CXmlSerializer(bool writeDefaults = false, GetCommentDelegate getCommentCallback = null) { _WriteDefaults = writeDefaults; _GetCommentCallback = getCommentCallback; } /// /// Uniform settings for writing XML files. ALWAYS use this! /// private readonly XmlWriterSettings _XmlSettings = new XmlWriterSettings { Indent = true, Encoding = Encoding.UTF8, ConformanceLevel = ConformanceLevel.Document }; /// /// Writes a value as a node or attribute /// /// XmlWriter to use /// Name of the node/attribute /// Type of the value /// Object to write /// When true, value will be written as an attribute to the currently open node, otherwhise it will create a new node /// Name of the sub nodes for array types(Array,List,Dictionary), can be set by XmlArrayItemAttribute and defaults to the type name /// If set, an attribute 'name' with this value will be written private void _WriteValue(XmlWriter writer, string name, Type type, object value, bool isAttribute, string arrayItemName = null, string nameAttribute = null) { if (!isAttribute && _GetCommentCallback != null) { var comment = _GetCommentCallback(name); if (!string.IsNullOrEmpty(comment)) { writer.WriteComment(comment); } } if (type.IsEnum || type == typeof(string) || type.IsPrimitive) { string strVal; try { strVal = (string)Convert.ChangeType(value, typeof(string), CultureInfo.InvariantCulture); } catch (Exception e) { throw new XmlException("Cannot convert value of type " + type.Name + " to string in node " + name + " (" + e.Message + ")"); } if (isAttribute) { writer.WriteAttributeString(name, strVal); } else { writer.WriteStartElement(name); if (nameAttribute != null) { writer.WriteAttributeString("name", nameAttribute); } if (!string.IsNullOrEmpty(strVal)) { writer.WriteValue(strVal); } writer.WriteEndElement(); } } else if (type == typeof(Guid)) { writer.WriteElementString(name, value.ToString()); } else if (type.IsNullable()) { _WriteValue(writer, name, type.GetGenericArguments()[0], value, isAttribute, arrayItemName, nameAttribute); } else if (type.IsList() || type.IsArray) { Debug.Assert(!isAttribute, "Lists cannot be attributes"); writer.WriteStartElement(name); if (nameAttribute != null) { writer.WriteAttributeString("name", nameAttribute); } var subType = type.IsArray ? type.GetElementType() : type.GetGenericArguments()[0]; var subName = arrayItemName ?? subType.GetTypeName(); var list = (IEnumerable)value; foreach (var subValue in list) { _WriteValue(writer, subName, subType, subValue, false); } writer.WriteEndElement(); } else if (type.IsDictionary()) { Debug.Assert(!isAttribute, "Dictionaries cannot be attributes"); writer.WriteStartElement(name); if (nameAttribute != null) { writer.WriteAttributeString("name", nameAttribute); } var subType = type.GetGenericArguments()[1]; var dict = (IDictionary)value; foreach (DictionaryEntry entry in dict) { string subName; string subNameAttribute; if (arrayItemName == null) { subName = (string)entry.Key; subNameAttribute = null; } else { subName = arrayItemName; subNameAttribute = (string)entry.Key; } _WriteValue(writer, subName, subType, entry.Value, false, null, subNameAttribute); } writer.WriteEndElement(); } else { Debug.Assert(!isAttribute, "Complex types cannot be attributes"); writer.WriteStartElement(name); if (nameAttribute != null) { writer.WriteAttributeString("name", nameAttribute); } if (value != null) { _WriteFields(writer, value); } writer.WriteEndElement(); } } /// /// Writes the fields of the given object, does not write the node tags itself /// /// XmlWriter to use /// Object to process private void _WriteFields(XmlWriter writer, object o) { var fields = o.GetType().GetFieldInfos(); foreach (var field in fields) { var value = field.GetValue(o); if (!_WriteDefaults && field.HasDefaultValue && Equals(value, field.DefaultValue)) { continue; } if (field.IsEmbeddedList) { var values = (IEnumerable)value; var empty = true; foreach (var subValue in values) { _WriteValue(writer, field.Name, field.SubType, subValue, field.IsAttribute); empty = false; } if (empty && _WriteDefaults) { writer.WriteElementString(field.Name, ""); } } else if (field.IsByteArray) { writer.WriteElementString(field.Name, Convert.ToBase64String((byte[])value)); } else { _WriteValue(writer, field.Name, field.Type, value, field.IsAttribute, field.ArrayItemName); } } } /// /// Serializes the given object using the writer /// /// XmlWriter to use /// Object to serialize /// (optional) Name of the root node (overwrites default value specified by XmlRoot/XmlTypeAttributes which defaults to "root") private void _Serialize(XmlWriter writer, object o, string rootNodeName) { if (string.IsNullOrEmpty(rootNodeName)) { var root = o.GetType().GetAttribute(); if (root != null && !string.IsNullOrEmpty(root.ElementName)) { rootNodeName = root.ElementName; } else { var typeAtt = o.GetType().GetAttribute(); if (typeAtt != null && !string.IsNullOrEmpty(typeAtt.TypeName)) { rootNodeName = typeAtt.TypeName; } else { rootNodeName = "root"; } } } try { writer.WriteStartDocument(); _WriteValue(writer, rootNodeName, o.GetType(), o, false, o.GetType().GetSubTypeName()); writer.WriteEndDocument(); } finally { writer.Flush(); } } /// /// Serializes the given object to the file /// /// Full path to file /// Object to serialize /// Name of the root node (overwrites default value specified by XmlRoot/XmlTypeAttributes which defaults to "root") public void Serialize(string filePath, object o, string rootNodeName = null) { using (var writer = XmlWriter.Create(filePath, _XmlSettings)) { _Serialize(writer, o, rootNodeName); } } /// /// Serializes the given object and returns the resulting xml as a string /// /// Object to serialize /// Name of the root node (overwrites default value specified by XmlRoot/XmlTypeAttributes which defaults to "root") /// Serialized object public string Serialize(object o, string rootNodeName = null) { var result = new MemoryStream(); using (var writer = XmlWriter.Create(result, _XmlSettings)) { _Serialize(writer, o, rootNodeName); } result.Position = 0; return new StreamReader(result).ReadToEnd(); } } }