* Apply R# clean-up for reliability - Migrate to NUnit4 Assert.That - Update dependencise versions - Fix LangVersion to 9.0 (until net core migration) - Reformat code - Apply 'var' style (easier for refactoring) - Remove redundant parentheses - Add braces on every condition blocks (1 line) - Uses new null checking patterns - Uses AutoProperties when possible - Uses string scalar type instead of class - Clean usings - Add .orig files in git ignore - Rename some properties to respect defined conventions and US format definition - ID -> Id - BPM -> Bpm - Finish -> End * Update R# settings according to the clean-up made
301 lines
11 KiB
C#
301 lines
11 KiB
C#
#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 <http://www.gnu.org/licenses/>.
|
|
#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;
|
|
|
|
/// <summary>
|
|
/// Creates a new serializer
|
|
/// </summary>
|
|
/// <param name="writeDefaults">When true, also nodes with default values will be written</param>
|
|
/// <param name="getCommentCallback">Callback that is called for each node and should return a string that will be used as a comment</param>
|
|
public CXmlSerializer(bool writeDefaults = false, GetCommentDelegate getCommentCallback = null)
|
|
{
|
|
_WriteDefaults = writeDefaults;
|
|
_GetCommentCallback = getCommentCallback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uniform settings for writing XML files. ALWAYS use this!
|
|
/// </summary>
|
|
private readonly XmlWriterSettings _XmlSettings = new XmlWriterSettings
|
|
{
|
|
Indent = true,
|
|
Encoding = Encoding.UTF8,
|
|
ConformanceLevel = ConformanceLevel.Document
|
|
};
|
|
|
|
/// <summary>
|
|
/// Writes a value as a node or attribute
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to use</param>
|
|
/// <param name="name">Name of the node/attribute</param>
|
|
/// <param name="type">Type of the value</param>
|
|
/// <param name="value">Object to write</param>
|
|
/// <param name="isAttribute">When true, value will be written as an attribute to the currently open node, otherwhise it will create a new node</param>
|
|
/// <param name="arrayItemName">Name of the sub nodes for array types(Array,List,Dictionary), can be set by XmlArrayItemAttribute and defaults to the type name</param>
|
|
/// <param name="nameAttribute">If set, an attribute 'name' with this value will be written</param>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the fields of the given object, does not write the node tags itself
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to use</param>
|
|
/// <param name="o">Object to process</param>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the given object using the writer
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to use</param>
|
|
/// <param name="o">Object to serialize</param>
|
|
/// <param name="rootNodeName">(optional) Name of the root node (overwrites default value specified by XmlRoot/XmlTypeAttributes which defaults to "root")</param>
|
|
private void _Serialize(XmlWriter writer, object o, string rootNodeName)
|
|
{
|
|
if (string.IsNullOrEmpty(rootNodeName))
|
|
{
|
|
var root = o.GetType().GetAttribute<XmlRootAttribute>();
|
|
if (root != null && !string.IsNullOrEmpty(root.ElementName))
|
|
{
|
|
rootNodeName = root.ElementName;
|
|
}
|
|
else
|
|
{
|
|
var typeAtt = o.GetType().GetAttribute<XmlTypeAttribute>();
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the given object to the file
|
|
/// </summary>
|
|
/// <param name="filePath">Full path to file</param>
|
|
/// <param name="o">Object to serialize</param>
|
|
/// <param name="rootNodeName">Name of the root node (overwrites default value specified by XmlRoot/XmlTypeAttributes which defaults to "root")</param>
|
|
public void Serialize(string filePath, object o, string rootNodeName = null)
|
|
{
|
|
using (var writer = XmlWriter.Create(filePath, _XmlSettings))
|
|
{
|
|
_Serialize(writer, o, rootNodeName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the given object and returns the resulting xml as a string
|
|
/// </summary>
|
|
/// <param name="o">Object to serialize</param>
|
|
/// <param name="rootNodeName">Name of the root node (overwrites default value specified by XmlRoot/XmlTypeAttributes which defaults to "root")</param>
|
|
/// <returns>Serialized object</returns>
|
|
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();
|
|
}
|
|
}
|
|
} |