#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();
}
}
}