Click here to Skip to main content
15,879,535 members
Articles / Programming Languages / XML

Yet Another XML Serialization Library for the .NET Framework

Rate me:
Please Sign up or sign in to vote.
4.92/5 (91 votes)
2 Oct 2012MIT24 min read 507.8K   269   207  
A flexible XML serialization library that lets developers design the XML file structure, and select the exception handling policy. YAXLib supports polymorphic serialization and serializing generic and non-generic collection classes and arrays.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml.Linq;
using System.IO;
using System.Diagnostics;
using System.Collections;
using System.Xml;
using System.Xml.Schema;

namespace YAXLib
{
    public class YAXSerializer
    {
        #region Private Members
        private Type m_type;
        private YAXExceptionHandlingPolicies m_exPolicy = YAXExceptionHandlingPolicies.ThrowErrorsOnly;
        private YAXParsingErrors m_parsingErrors = new YAXParsingErrors();
        private YAXExceptionTypes m_defaultExceptionType = YAXExceptionTypes.Warning;
        private YAXSerializationOptions m_serializationOption = YAXSerializationOptions.SerializeNullObjects;

        /// <summary>
        /// Gets the parsing errors.
        /// </summary>
        /// <value>The parsing errors.</value>
        public YAXParsingErrors ParsingErrors
        {
            get
            {
                return m_parsingErrors;
            }
        }

        #endregion

        #region Construction Stuff
        /// <summary>
        /// Initializes a new instance of the <see cref="YAXSerializer"/> class.
        /// </summary>
        /// <param name="t">The type of the object being serialized/deserialized.</param>
        public YAXSerializer(Type t)
            : this(t, YAXExceptionHandlingPolicies.ThrowWarningsAndErrors, YAXExceptionTypes.Error, YAXSerializationOptions.SerializeNullObjects)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="YAXSerializer"/> class.
        /// </summary>
        /// <param name="t">The type of the object being serialized/deserialized.</param>
        /// <param name="exPolicy">The exception handling policy.</param>
        public YAXSerializer(Type t, YAXExceptionHandlingPolicies exPolicy)
            : this(t, exPolicy, YAXExceptionTypes.Error, YAXSerializationOptions.SerializeNullObjects)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="YAXSerializer"/> class.
        /// </summary>
        /// <param name="t">The type of the object being serialized/deserialized.</param>
        /// <param name="exPolicy">The exception handling policy.</param>
        /// <param name="defaultExType">The exceptions are treated as the value specified, unless otherwise specified.</param>
        public YAXSerializer(Type t, YAXExceptionHandlingPolicies exPolicy, YAXExceptionTypes defaultExType)
            : this(t, exPolicy, defaultExType, YAXSerializationOptions.SerializeNullObjects)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="YAXSerializer"/> class.
        /// </summary>
        /// <param name="t">The type of the object being serialized/deserialized.</param>
        /// <param name="exPolicy">The exception handling policy.</param>
        /// <param name="defaultExType">The exceptions are treated as the value specified, unless otherwise specified.</param>
        /// <param name="option">The serialization option.</param>
        public YAXSerializer(Type t, YAXExceptionHandlingPolicies exPolicy, YAXExceptionTypes defaultExType, YAXSerializationOptions option)
        {
            this.m_type = t;
            this.m_exPolicy = exPolicy;
            this.m_defaultExceptionType = defaultExType;
            this.m_serializationOption = option;
        }


        #endregion

        #region Utilities
        /// <summary>
        /// Gets the friendly name for the type. Recommended for generic types.
        /// </summary>
        /// <param name="type">The type to get its friendly name</param>
        /// <returns></returns>
        private static string GetTypeFriendlyName(Type type)
        {
            if (type == null)
                throw new System.ArgumentNullException("type");

            string name = type.Name;
            if (type.IsGenericType)
            {
                int backqIndex = name.IndexOf('`');
                if (backqIndex == 0)
                {
                    throw new InvalidOperationException("Bad type name: " + name);
                }
                else if (backqIndex > 0)
                {
                    name = name.Substring(0, backqIndex);
                }

                name += "Of";

                foreach (Type genType in type.GetGenericArguments())
                {
                    name += GetTypeFriendlyName(genType);
                }
            }

            return name;
        }

        /// <summary>
        /// Determines whether the type specified contains generic parameters or not.
        /// </summary>
        /// <param name="type">The type to check.</param>
        /// <returns></returns>
        private static bool TypeContainsGenericParameters(Type type)
        {
            if (type == null)
                throw new System.ArgumentNullException("type");

            if (type.IsGenericType)
            {
                foreach (Type genType in type.GetGenericArguments())
                {
                    if (genType.IsGenericParameter)
                        return true;
                    else if (TypeContainsGenericParameters(genType))
                        return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Gets the type of the items of a collection type.
        /// </summary>
        /// <param name="type">The type of the collection.</param>
        /// <returns>the type of the items of a collection type.</returns>
        private Type GetCollectionItemType(Type type)
        {
            Type itemType = typeof(object);

            if (type.IsInterface && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            {
                itemType = type.GetGenericArguments()[0];
            }
            else if (type.IsInterface && type == typeof(IEnumerable))
            {
                itemType = typeof(object);
            }
            else
            {
                foreach (Type interfaceType in type.GetInterfaces())
                {
                    if (interfaceType.IsGenericType &&
                        interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                    {
                        itemType = interfaceType.GetGenericArguments()[0];
                    }
                }
            }

            return itemType;
        }

        /// <summary>
        /// Determines whether the specified type is basic type. A basic type is one that can be wholly expressed
        /// as an XML attribute. All primitive data types and type <c>string</c> and <c>DataTime</c> are basic.
        /// </summary>
        /// <param name="t">The type</param>
        private static bool IsBasicType(Type t)
        {
            if (t == typeof(string) || t.IsPrimitive || t.IsEnum || t == typeof(DateTime))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Determines whether the specified type is a collection type.
        /// </summary>
        /// <param name="t">The type to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified type is a collection type; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsCollectionType(Type t)
        {
            if (t == typeof(string)) 
                return false;

            return typeof(IEnumerable).IsAssignableFrom(t);
        }

        /// <summary>
        /// Determines whether the specified type is array.
        /// </summary>
        /// <param name="t">The type</param>
        /// <returns>
        /// 	<c>true</c> if the specified type is array; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsArray(Type t)
        {
            return (t.BaseType == typeof(System.Array));
        }

        /// <summary>
        /// Determines whether the specified type is <c>List</c>.
        /// </summary>
        /// <param name="t">The type to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified type is list; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsList(Type t)
        {
            foreach (Type interfaceType in t.GetInterfaces())
            {
                if (interfaceType.IsGenericType &&
                    interfaceType.GetGenericTypeDefinition() == typeof(IList<>))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines whether the specified type has implemented the ICollection interface.
        /// </summary>
        /// <param name="t">The type to check.</param>
        /// <param name="itemType">Type of the member items.</param>
        /// <returns>
        /// 	<c>true</c> if the specified type has implemented the ICollection interface; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsICollection(Type t, out Type itemType)
        {
            itemType = typeof(object);

            if (t.IsInterface && t.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                itemType = t.GetGenericArguments()[0];
                return true;
            }

            foreach (Type interfaceType in t.GetInterfaces())
            {
                if (interfaceType.IsGenericType &&
                    interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
                {
                    itemType = interfaceType.GetGenericArguments()[0];
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines whether the specified type is a generic dictionary.
        /// </summary>
        /// <param name="type">The type to check.</param>
        /// <param name="keyType">Type of the key.</param>
        /// <param name="valueType">Type of the value.</param>
        /// <returns>
        /// 	<c>true</c> if the specified type is dictionary; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsDictionary(Type type, out Type keyType, out Type valueType)
        {
            keyType = typeof(object);
            valueType = typeof(object);

            foreach (Type interfaceType in type.GetInterfaces())
            {
                if (interfaceType.IsGenericType &&
                    interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
                {
                    Type[] genArgs = interfaceType.GetGenericArguments();
                    keyType = genArgs[0];
                    valueType = genArgs[1];
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determines whether the specified type is a generic dictionary.
        /// </summary>
        /// <param name="type">The type to check.</param>
        /// <returns>
        /// 	<c>true</c> if the specified type is dictionary; otherwise, <c>false</c>.
        /// </returns>
        private bool IsDictionary(Type type)
        {
            Type keyType, valueType;
            return IsDictionary(type, out keyType, out valueType);
        }

        /// <summary>
        /// Determines whether the specified type has implemented or is an <c>IEnumerable</c> or <c>IEnumerable&lt;&gt;</c> .
        /// </summary>
        /// <param name="type">The type to check.</param>
        /// <param name="seqType">Type of the sequence items.</param>
        /// <returns>
        /// 	<c>true</c> if the specified type is enumerable; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsEnumerable(Type type, out Type seqType)
        {
            seqType = typeof(object);
            bool isNongenericEnumerable = false;

            if (type.IsInterface && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            {
                seqType = type.GetGenericArguments()[0];
                return true;
            }

            foreach (Type interfaceType in type.GetInterfaces())
            {
                if (interfaceType.IsGenericType &&
                    interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                {
                    Type[] genArgs = interfaceType.GetGenericArguments();
                    seqType = genArgs[0];
                    return true;
                }
                else if(interfaceType == typeof(IEnumerable))
                {
                    isNongenericEnumerable = true;
                }
            }

            if (isNongenericEnumerable)
                return true;

            return false;
        }

        /// <summary>
        /// Called when an exception occurs inside the library. It applies the exception handling policies.
        /// </summary>
        /// <param name="ex">The exception that has occurred.</param>
        /// <param name="exType">Type of the exception.</param>
        private void OnExceptionOccurred(YAXException ex, YAXExceptionTypes exType)
        {
            if (exType == YAXExceptionTypes.Ignore)
                return;

            m_parsingErrors.AddException(ex, exType);
            if ((m_exPolicy == YAXExceptionHandlingPolicies.ThrowWarningsAndErrors) ||
                (m_exPolicy == YAXExceptionHandlingPolicies.ThrowErrorsOnly && exType == YAXExceptionTypes.Error))
                throw ex;
        }

        /// <summary>
        /// Tries to format the specified object using the format string provided.
        /// If the formatting operation is not applicable, the source object is returned intact.
        /// Note: The type of the returned object will be 'System.String' if formatting succeeds.
        /// </summary>
        /// <param name="src">The source object.</param>
        /// <param name="format">The format string.</param>
        /// <returns></returns>
        private object TryFormatObject(object src, string format)
        {
            if (format == null || src == null)
                return src;

            object formattedObject = null;
            try
            {
                formattedObject = src.GetType().InvokeMember("ToString", BindingFlags.InvokeMethod, null, src, new object[] { format } );
            }
            catch
            {
                OnExceptionOccurred(new YAXInvalidFormatProvided(src.GetType(), format), m_defaultExceptionType);
            }

            return (formattedObject != null) ? formattedObject : src;
        }

        #endregion

        #region Serialization Stuff

        /// <summary>
        /// Serializes the specified object and returns a string containing the XML.
        /// </summary>
        /// <param name="obj">The object to serialize.</param>
        /// <returns></returns>
        public string Serialize(object obj)
        {
            return SerializeBase(obj).ToString();
        }

        /// <summary>
        /// Serializes the specified object into a <c>TextWriter</c> instance.
        /// </summary>
        /// <param name="obj">The object to serialize.</param>
        /// <param name="textWriter">The <c>TextWriter</c> instance.</param>
        public void Serialize(object obj, TextWriter textWriter)
        {
            textWriter.Write(Serialize(obj));
        }

        /// <summary>
        /// Serializes the specified object into a <c>XmlWriter</c> instance.
        /// </summary>
        /// <param name="obj">The object to serialize.</param>
        /// <param name="xmlWriter">The <c>XmlWriter</c> instance.</param>
        public void Serialize(object obj, XmlWriter xmlWriter)
        {
            SerializeBase(obj).WriteTo(xmlWriter);
        }

        /// <summary>
        /// Serializes the specified object to file.
        /// </summary>
        /// <param name="obj">The object to serialize.</param>
        /// <param name="fileName">Path to the file.</param>
        public void SerializeToFile(object obj, string fileName)
        {
            string ser = String.Format("{0}{1}{2}", 
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>", 
                Environment.NewLine,
                Serialize(obj));
            File.WriteAllText(fileName, ser, Encoding.UTF8);
        }

        private XElement MakeDictionaryElement(string elementName, object elementValue, YAXDictionaryAttribute dicAttrInst, YAXCollectionAttribute collectionAttrInst)
        {
            if (elementValue == null)
                return new XElement(elementName, elementValue);

            Type keyType, valueType;
            if(!IsDictionary(elementValue.GetType(), out keyType, out valueType))
                throw new ArgumentException("elementValue must be a Dictionary");

            IEnumerable dicInst = elementValue as IEnumerable;
            string eachElementName = null;
            bool isKeyAttrib = false;
            bool isValueAttrib = false;
            string keyFormat = null;
            string valueFormat = null;
            string keyAlias = "Key";
            string valueAlias = "Value";

            if (collectionAttrInst != null)
            {
                eachElementName = collectionAttrInst.EachElementName;
            }

            if (dicAttrInst != null)
            {
                eachElementName = dicAttrInst.EachPairName ?? eachElementName;
                if (dicAttrInst.SerializeKeyAs == YAXNodeTypes.Attribute)
                    isKeyAttrib = IsBasicType(keyType);

                if (dicAttrInst.SerializeValueAs == YAXNodeTypes.Attribute)
                    isValueAttrib = IsBasicType(valueType);

                keyFormat = dicAttrInst.KeyFormatString;
                valueFormat = dicAttrInst.ValueFormatString;

                keyAlias = dicAttrInst.KeyName ?? "Key";
                valueAlias = dicAttrInst.ValueName ?? "Value";
            }

            XElement elem = new XElement(elementName, null);
            foreach (object obj in dicInst)
            {
                object keyObj = obj.GetType().GetProperty("Key").GetValue(obj, null);
                object valueObj = obj.GetType().GetProperty("Value").GetValue(obj, null);

                if (keyFormat != null)
                    keyObj = TryFormatObject(keyObj, keyFormat);

                if (valueFormat != null)
                    valueObj = TryFormatObject(valueObj, valueFormat);

                XElement elemChild = new XElement(eachElementName ?? GetTypeFriendlyName(obj.GetType()), null);

                if (isKeyAttrib)
                    elemChild.Add(new XAttribute(keyAlias, keyObj ?? ""));
                else
                    elemChild.Add(MakeElement(keyAlias, keyObj));

                if (isValueAttrib)
                    elemChild.Add(new XAttribute(valueAlias, valueObj ?? ""));
                else
                    elemChild.Add(MakeElement(valueAlias, valueObj));

                elem.Add(elemChild);
            }

            return elem;
        }

        private XElement MakeCollectionElement(string elementName, object elementValue, YAXCollectionAttribute collectionAttrInst, string format)
        {
            if (elementValue == null)
                return new XElement(elementName, elementValue);

            if (!(elementValue is IEnumerable))
                throw new ArgumentException("elementValue must be an IEnumerable");

            IEnumerable collectionInst = elementValue as IEnumerable;
            YAXCollectionSerializationTypes serType = YAXCollectionSerializationTypes.Recursive;
            string seperator = "";
            string eachElementName = null;

            if (collectionAttrInst != null)
            {
                serType = collectionAttrInst.SerializationType;
                seperator = collectionAttrInst.SeparateBy;
                eachElementName = collectionAttrInst.EachElementName;
            }

            if (eachElementName == null)
            {
                Type colItemType = GetCollectionItemType(elementValue.GetType());
                eachElementName = GetTypeFriendlyName(colItemType);
            }

            if (serType == YAXCollectionSerializationTypes.Serially)
            {
                StringBuilder sb = new StringBuilder();

                bool isFirst = true;
                object objToAdd = null;
                foreach(object obj in collectionInst)
                {
                    objToAdd = (format == null) ? obj : TryFormatObject(obj, format);
                    if(isFirst)
                    {
                        sb.Append(objToAdd.ToString());
                        isFirst = false;
                    }
                    else
                    {
                        sb.AppendFormat("{0}{1}", seperator, objToAdd.ToString());
                    }
                }

                return MakeElement(elementName, sb.ToString());
            }
            else
            {
                XElement elem = new XElement(elementName, null);
                object objToAdd = null;

                foreach (object obj in collectionInst)
                {
                    objToAdd = (format == null) ? obj : TryFormatObject(obj, format);
                    elem.Add(MakeElement(eachElementName, objToAdd));
                }

                return elem;
            }
        }

        private XElement MakeElement(string name, object value)
        {
            if (value == null || IsBasicType(value.GetType()))
                return new XElement(name, value);
            else
            {
                YAXSerializer ser = new YAXSerializer(value.GetType(), m_exPolicy, m_defaultExceptionType, m_serializationOption);
                XElement elem = ser.SerializeBase(value, name);
                m_parsingErrors.AddRange(ser.ParsingErrors);
                return elem;
            }
        }

        protected XElement SerializeBase(object obj)
        {
            if (!m_type.IsInstanceOfType(obj))
                throw new YAXObjectTypeMismatch(m_type, obj.GetType());

            string className = GetTypeFriendlyName(obj.GetType());

            foreach (var classAttr in obj.GetType().GetCustomAttributes(true))
            {
                if (classAttr is YAXSerializeAsAttribute)
                {
                    className = (classAttr as YAXSerializeAsAttribute).SerializeAs;
                }
            }

            return SerializeBase(obj, className);
        }

        protected XElement SerializeBase(object obj, string className)
        {
            XElement baseElement = new XElement(
                className, null);

            // find interesting class attributes
            foreach (var classAttr in obj.GetType().GetCustomAttributes(true))
            {
                if (classAttr is YAXCommentAttribute)
                {
                    baseElement.Add(new XComment((classAttr as YAXCommentAttribute).Comment));
                }
            }

            // iterate through public properties
            foreach(var p in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                string elementName = "";
                object elementValue = null;

                bool dontSerialize = false;
                bool isClassAttr = false;
                bool attributeFor = false;
                bool elementFor = false;
                string format = null;
                string parentElement = "";
                bool isNotCollection = false; // Set by the [YAXNotCollection] Attribute
                bool isCollection = IsCollectionType(p.PropertyType);
                YAXCollectionAttribute collectionAttrInst = null;
                bool isDictionary = IsDictionary(p.PropertyType);
                YAXDictionaryAttribute dictionaryAttrInst = null;

                if (p.CanRead)
                {
                    if (p.PropertyType == m_type)
                    {
                        throw new YAXCannotSerializeSelfReferentialTypes(m_type);
                    }

                    elementName = p.Name;
                    elementValue = p.GetValue(obj, null);

                    if (elementValue == null && IsNotAllowdNullObjectSerialization())
                        continue;

                    foreach (var attr in p.GetCustomAttributes(true))
                    {
                        if (attr is YAXSerializeAsAttribute)
                        {
                            elementName = (attr as YAXSerializeAsAttribute).SerializeAs;
                        }
                        else if (attr is YAXDontSerializeAttribute)
                        {
                            dontSerialize = true;
                        }
                        else if (attr is YAXAttributeForClassAttribute)
                        {
                            isClassAttr = IsBasicType(p.PropertyType);
                        }
                        else if (attr is YAXAttributeForAttribute)
                        {
                            attributeFor = IsBasicType(p.PropertyType);
                            parentElement = (attr as YAXAttributeForAttribute).Parent;
                        }
                        else if (attr is YAXElementForAttribute)
                        {
                            elementFor = true;
                            parentElement = (attr as YAXElementForAttribute).Parent;
                        }
                        else if (attr is YAXFormatAttribute)
                        {
                            format = (attr as YAXFormatAttribute).Format;
                        }
                        else if (attr is YAXCollectionAttribute)
                        {
                            collectionAttrInst = attr as YAXCollectionAttribute;
                        }
                        else if (attr is YAXDictionaryAttribute)
                        {
                            dictionaryAttrInst = attr as YAXDictionaryAttribute;
                        }
                        else if (attr is YAXNotCollectionAttribute)
                        {
                            isNotCollection = true;
                        }
                    }
                    if (isNotCollection)
                        isCollection = isDictionary = false;

                    if (format != null && !isCollection)
                    {
                        // do the formatting. If formatting succeeds the type of 
                        // the elementValue will become 'System.String'
                        elementValue = TryFormatObject(elementValue, format);
                    }

                    if (!dontSerialize)
                    {
                        if (isClassAttr) // it gets true only for basic data types
                        {
                            if (baseElement.Attribute(elementName) == null)
                                baseElement.Add(new XAttribute(elementName, elementValue ?? ""));
                            else
                                throw new YAXAttributeAlreadyExistsException(elementName);
                        }
                        else
                        {
                            if (attributeFor || elementFor)
                            {
                                XElement parElem = baseElement.Element(parentElement);
                                if (parElem == null)
                                {
                                    parElem = new XElement(parentElement);
                                    baseElement.Add(parElem);
                                }

                                if (attributeFor)
                                {
                                    if (parElem.Attribute(elementName) != null)
                                        throw new YAXAttributeAlreadyExistsException(elementName);
                                    else
                                        parElem.Add(new XAttribute(elementName, elementValue ?? ""));
                                }
                                else if (elementFor)
                                {
                                    XElement elemToAdd;
                                    if (isDictionary)
                                        elemToAdd = MakeDictionaryElement(elementName, elementValue, dictionaryAttrInst, collectionAttrInst);
                                    else if (isCollection)
                                        elemToAdd = MakeCollectionElement(elementName, elementValue, collectionAttrInst, format);
                                    else
                                        elemToAdd = MakeElement(elementName, elementValue);

                                    parElem.Add(elemToAdd);
                                }
                            }
                            else
                            {
                                XElement t = baseElement.Element(elementName);
                                if (t == null)
                                {
                                    if (isDictionary)
                                        baseElement.Add(MakeDictionaryElement(elementName, elementValue, dictionaryAttrInst, collectionAttrInst));
                                    else if (isCollection)
                                        baseElement.Add(MakeCollectionElement(elementName, elementValue, collectionAttrInst, format));
                                    else
                                        baseElement.Add(MakeElement(elementName, elementValue));
                                }
                                else
                                {
                                    if (IsBasicType(p.PropertyType))
                                    {
                                        t.SetValue(elementValue);
                                    }
                                    else
                                    {
                                        XElement elemToAdd;
                                        if (isDictionary)
                                            elemToAdd = MakeDictionaryElement(elementName, elementValue, dictionaryAttrInst, collectionAttrInst);
                                        else if (isCollection)
                                            elemToAdd = MakeCollectionElement(elementName, elementValue, collectionAttrInst, format);
                                        else
                                            elemToAdd = MakeElement(elementName, elementValue);
                                        
                                        MoveDescendants(elemToAdd, t);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return baseElement;
        }

        private bool IsNotAllowdNullObjectSerialization()
        {
            return m_serializationOption == YAXSerializationOptions.DontSerializeNullObjects;
        }

        private static void MoveDescendants(XElement src, XElement dst)
        {
            foreach (XAttribute attr in src.Attributes())
            {
                if (dst.Attribute(attr.Name) != null)
                {
                    throw new YAXAttributeAlreadyExistsException(attr.Name.ToString());
                }

                dst.Add(attr);
            }

            foreach (XElement elem in src.Elements())
            {
                dst.Add(elem);
            }
        }

        #endregion

        #region Deserialization Stuff

        /// <summary>
        /// Deserializes the specified string containing the XML serialization and returns an object.
        /// </summary>
        /// <param name="input">The input string containing the XML serialization.</param>
        /// <returns></returns>
        public object Deserialize(string input)
        {
            try
            {
                TextReader tr = new StringReader(input);
                XDocument xdoc = XDocument.Load(tr);

                XElement baseElement = xdoc.Root;

                return DeserializeBase(baseElement);
            }
            catch (XmlException ex)
            {
                OnExceptionOccurred(new YAXBadlyFormedXML(ex), m_defaultExceptionType);
                return null;
            }
        }

        /// <summary>
        /// Deserializes an object while reading input from an instance of <c>XmlReader</c>.
        /// </summary>
        /// <param name="xmlReader">The <c>XmlReader</c> instance to read input from.</param>
        /// <returns></returns>
        public object Deserialize(XmlReader xmlReader)
        {
            try
            {
                XDocument xdoc = XDocument.Load(xmlReader);
                XElement baseElement = xdoc.Root;
                return DeserializeBase(baseElement);
            }
            catch (XmlException ex)
            {
                OnExceptionOccurred(new YAXBadlyFormedXML(ex), m_defaultExceptionType);
                return null;
            }
        }

        /// <summary>
        /// Deserializes an object while reading input from an instance of <c>TextReader</c>.
        /// </summary>
        /// <param name="textReader">The <c>TextReader</c> instance to read input from.</param>
        /// <returns></returns>
        public object Deserialize(TextReader textReader)
        {
            try
            {
                XDocument xdoc = XDocument.Load(textReader);                
                XElement baseElement = xdoc.Root;
                return DeserializeBase(baseElement);
            }
            catch (XmlException ex)
            {
                OnExceptionOccurred(new YAXBadlyFormedXML(ex), m_defaultExceptionType);
                return null;
            }
        }

        /// <summary>
        /// Deserializes an object from the specified file which contains the XML serialization of the object.
        /// </summary>
        /// <param name="fileName">Path to the file.</param>
        /// <returns></returns>
        public object DeserializeFromFile(string fileName)
        {
            try
            {
                return Deserialize(File.ReadAllText(fileName));
            }
            catch (XmlException ex)
            {
                OnExceptionOccurred(new YAXBadlyFormedXML(ex), m_defaultExceptionType);
                return null;
            }
        }

        private object ConvertBasicType(object value, Type dstType)
        {
            if (!IsBasicType(dstType))
                throw new ArgumentException("Destination type must be a basic type", "dstType");

            object convertedObj = null;
            if (dstType.IsEnum)
                convertedObj = Enum.Parse(dstType, value.ToString());
            else
                convertedObj = Convert.ChangeType(value, dstType);

            return convertedObj;
        }

        private object DeserializeKeyValuePair(XElement baseElement)
        {
            Type[] genArgs = m_type.GetGenericArguments();
            Type keyType = genArgs[0];
            Type valueType = genArgs[1];

            object keyValue, valueValue;
            if (IsBasicType(keyType))
            {
                try
                {
                    keyValue = ConvertBasicType(baseElement.Element("Key").Value, keyType);
                }
                catch (NullReferenceException)
                {
                    keyValue = null;
                }
            }
            else
            {
                YAXSerializer ser = new YAXSerializer(keyType, m_exPolicy, m_defaultExceptionType, m_serializationOption);
                keyValue = ser.DeserializeBase(baseElement.Element("Key"));
                m_parsingErrors.AddRange(ser.ParsingErrors);
            }

            if (IsBasicType(valueType))
            {
                try
                {
                    valueValue = ConvertBasicType(baseElement.Element("Value").Value, valueType);
                }
                catch (NullReferenceException)
                {
                    valueValue = null;
                }
            }
            else
            {
                YAXSerializer ser = new YAXSerializer(valueType, m_exPolicy, m_defaultExceptionType, m_serializationOption);
                valueValue = ser.DeserializeBase(baseElement.Element("Value"));
                m_parsingErrors.AddRange(ser.ParsingErrors);
            }

            object pair = m_type.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, null, null, new object[] {keyValue, valueValue});

            return pair;
        }

        private object DeserializeBase(XElement baseElement)
        {
            if (baseElement == null)
                return null;

            if (m_type.IsGenericType && m_type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
                return DeserializeKeyValuePair(baseElement);

            object o = m_type.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, null, null, new object[0]);
            foreach (var prop in m_type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (prop.CanWrite)
                {
                    string propName = prop.Name;
                    string elemValue = "";
                    XElement tElem = null;
                    object convertedObj = null;

                    bool dontSerialize = false;
                    bool isClassAttr = false;
                    bool attributeFor = false;
                    bool elementFor = false;
                    string parentElement = "";
                    string format = null;
                    YAXExceptionTypes exType = m_defaultExceptionType;
                    object exDefaultValue = null;
                    bool isNotCollection = false;
                    bool isCollection = IsCollectionType(prop.PropertyType);
                    YAXCollectionAttribute collectionAttrInst = null;
                    bool isDictionary = IsDictionary(prop.PropertyType);
                    YAXDictionaryAttribute dictionaryAttrInst = null;

                    // state variable 
                    bool exOccurred = false;

                    foreach (var propAttr in prop.GetCustomAttributes(true))
                    {
                        if (propAttr is YAXSerializeAsAttribute)
                        {
                            propName = (propAttr as YAXSerializeAsAttribute).SerializeAs;
                        }
                        else if (propAttr is YAXDontSerializeAttribute)
                        {
                            dontSerialize = true;
                        }
                        else if (propAttr is YAXAttributeForClassAttribute)
                        {
                            isClassAttr = IsBasicType( prop.PropertyType );
                        }
                        else if (propAttr is YAXAttributeForAttribute)
                        {
                            attributeFor = IsBasicType(prop.PropertyType);
                            parentElement = (propAttr as YAXAttributeForAttribute).Parent;
                        }
                        else if (propAttr is YAXElementForAttribute)
                        {
                            elementFor = true;
                            parentElement = (propAttr as YAXElementForAttribute).Parent;
                        }
                        else if (propAttr is YAXFormatAttribute)
                        {
                            format = (propAttr as YAXFormatAttribute).Format;
                        }
                        else if (propAttr is YAXCollectionAttribute)
                        {
                            collectionAttrInst = propAttr as YAXCollectionAttribute;
                        }
                        else if (propAttr is YAXDictionaryAttribute)
                        {
                            dictionaryAttrInst = propAttr as YAXDictionaryAttribute;
                        }
                        else if (propAttr is YAXErrorIfMissedAttribute)
                        {
                            YAXErrorIfMissedAttribute theErrAttr = (propAttr as YAXErrorIfMissedAttribute);
                            exType = theErrAttr.TreatAs;
                            exDefaultValue = theErrAttr.DefaultValue;
                        }
                        else if (propAttr is YAXNotCollectionAttribute)
                        {
                            isNotCollection = true;
                        }
                    }

                    if (isNotCollection)
                        isCollection = isDictionary = false;

                    if (!dontSerialize)
                    {
                        #region Trying to assign element value in proper objects to be retrieved later
                        if (!baseElement.HasAttributes && !baseElement.HasElements && baseElement.Value == "")
                        {
                            if (isCollection)
                                return o; //exDefaultValue;
                            else
                            {
                                return IsNotAllowdNullObjectSerialization() ? o : null;
                            }
                        }

                        //if (!baseElement.HasAttributes && !baseElement.HasElements && baseElement.Value == "" && m_type != typeof(string))
                        //    return null; //exDefaultValue;

                        if (isClassAttr)
                        {
                            XAttribute tattr = baseElement.Attribute(propName);
                            if (tattr == null)
                            {
                                OnExceptionOccurred(new YAXAttributeMissingException(propName), exType);
                                exOccurred = true;
                            }
                            else
                            {
                                elemValue = tattr.Value;
                            }
                        }
                        else
                        {
                            if (attributeFor || elementFor)
                            {
                                XElement parElem = baseElement.Element(parentElement);
                                if (parElem == null)
                                {
                                    OnExceptionOccurred(new YAXElementMissingException(parentElement), m_defaultExceptionType);
                                    exOccurred = true;
                                }

                                if (attributeFor)
                                {
                                    XAttribute tAttr = parElem.Attribute(propName);
                                    if (tAttr != null)
                                    {
                                        elemValue = tAttr.Value;
                                    }
                                    else
                                    {
                                        OnExceptionOccurred(new YAXAttributeMissingException(propName), exType);
                                        exOccurred = true;
                                    }
                                }
                                else if (elementFor)
                                {
                                    tElem = parElem.Element(propName);
                                    if (tElem != null)
                                    {
                                        elemValue = tElem.Value;
                                    }
                                    else
                                    {
                                        OnExceptionOccurred(new YAXElementMissingException(propName), exType);
                                        exOccurred = true;
                                    }
                                }
                            }
                            else
                            {
                                tElem = baseElement.Element(propName);
                                if (tElem != null)
                                {
                                    elemValue = tElem.Value;
                                }
                                else
                                {
                                    OnExceptionOccurred(new YAXElementMissingException(propName), exType);
                                    exOccurred = true;
                                }
                            }
                        }
                        #endregion

                        #region Now try to retrieve elemValue's value
                        // also note that tElem refers to the XElement instance containing our desired data

                        if (exOccurred) // if some error/exception has occurred
                        {
                            if (exDefaultValue != null)
                            {
                                try
                                {
                                    prop.SetValue(o, exDefaultValue, null);
                                }
                                catch
                                {
                                    OnExceptionOccurred(new YAXDefaultValueCannotBeAssigned(propName, exDefaultValue), m_defaultExceptionType);
                                    exOccurred = true;
                                }
                            }
                            else
                            {
                                if (!prop.PropertyType.IsValueType)
                                {
                                    prop.SetValue(o, null /*the value to be assigned */, null);
                                }
                            }
                        }
                        else if (elemValue != null)
                        {
                            Type pType = prop.PropertyType;

                            if (pType == typeof(string))
                            {
                                try
                                {
                                    prop.SetValue(o, elemValue, null);
                                }
                                catch
                                {
                                    OnExceptionOccurred(new YAXPropertyCannotBeAssignedTo(propName), m_defaultExceptionType);
                                    exOccurred = true;
                                }
                            }
                            else
                            {
                                if (IsBasicType(pType))
                                {
                                    try
                                    {
                                        convertedObj = ConvertBasicType(elemValue, pType);
                                        try
                                        {
                                            prop.SetValue(o, convertedObj, null);
                                        }
                                        catch
                                        {
                                            OnExceptionOccurred(new YAXPropertyCannotBeAssignedTo(propName), m_defaultExceptionType);
                                            exOccurred = true;
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        if (ex is YAXException)
                                            throw;

                                        OnExceptionOccurred(new YAXBadlyFormedInput(propName, elemValue), exType);
                                        exOccurred = true;

                                        try
                                        {
                                            prop.SetValue(o, exDefaultValue, null);
                                        }
                                        catch
                                        {
                                            OnExceptionOccurred(new YAXDefaultValueCannotBeAssigned(propName, exDefaultValue), m_defaultExceptionType);
                                            exOccurred = true;
                                        }
                                    }

                                }
                                else // p's type is not even primitive
                                {
                                    if (isDictionary && dictionaryAttrInst != null) // otherwise the "else if(isCollection)" block solves the problem
                                    {
                                        Type keyType, valueType;
                                        if (!IsDictionary(pType, out keyType, out valueType))
                                            throw new Exception("elemValue must be a Dictionary");

                                        Type pairType = null;
                                        IsEnumerable(pType, out pairType);
                                        string eachElementName = GetTypeFriendlyName(pairType); 
                                        bool isKeyAttrib = false;
                                        bool isValueAttrib = false;
                                        string keyAlias = "Key";
                                        string valueAlias = "Value";

                                        if (collectionAttrInst != null)
                                        {
                                            eachElementName = collectionAttrInst.EachElementName ?? eachElementName;
                                        }

                                        if (dictionaryAttrInst != null)
                                        {
                                            eachElementName = dictionaryAttrInst.EachPairName ?? eachElementName;
                                            if (dictionaryAttrInst.SerializeKeyAs == YAXNodeTypes.Attribute)
                                                isKeyAttrib = IsBasicType(keyType);

                                            if (dictionaryAttrInst.SerializeValueAs == YAXNodeTypes.Attribute)
                                                isValueAttrib = IsBasicType(valueType);

                                            keyAlias = dictionaryAttrInst.KeyName ?? keyAlias;
                                            valueAlias = dictionaryAttrInst.ValueName ?? valueAlias;
                                        }

                                        object dic = prop.PropertyType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, null, null, new object[0]);

                                        object key = null, value = null;
                                        YAXSerializer keySer = null, valueSer = null; 
                                        foreach (XElement childElem in tElem.Elements(eachElementName))
                                        {
                                            if (isKeyAttrib)
                                                key = ConvertBasicType(childElem.Attribute(keyAlias).Value, keyType);
                                            else if (IsBasicType(keyType))
                                                key = ConvertBasicType(childElem.Element(keyAlias).Value, keyType);
                                            else
                                            {
                                                if(keySer == null)
                                                    keySer = new YAXSerializer(keyType, m_exPolicy, m_defaultExceptionType, m_serializationOption);

                                                key = keySer.DeserializeBase(childElem.Element(keyAlias));
                                                m_parsingErrors.AddRange(keySer.ParsingErrors);
                                            }

                                            if(isValueAttrib)
                                                value = ConvertBasicType(childElem.Attribute(valueAlias).Value, valueType);
                                            else if (IsBasicType(valueType))
                                                value = ConvertBasicType(childElem.Element(valueAlias).Value, valueType);
                                            else
                                            {
                                                if (valueSer == null)
                                                    valueSer = new YAXSerializer(valueType, m_exPolicy, m_defaultExceptionType, m_serializationOption);

                                                value = valueSer.DeserializeBase(childElem.Element(valueAlias));
                                                m_parsingErrors.AddRange(valueSer.ParsingErrors);
                                            }
                                                    
                                            try
                                            {
                                                prop.PropertyType.InvokeMember("Add", BindingFlags.InvokeMethod, null, dic, new object[] { key, value });
                                            }
                                            catch
                                            {
                                                OnExceptionOccurred(new YAXCannotAddObjectToCollection(propName, new KeyValuePair<object, object>( key, value )), 
                                                                    m_defaultExceptionType);
                                                exOccurred = true;
                                            }
                                        }

                                        try
                                        {
                                            prop.SetValue(o, dic, null);
                                        }
                                        catch
                                        {
                                            OnExceptionOccurred(new YAXPropertyCannotBeAssignedTo(propName), m_defaultExceptionType);
                                            exOccurred = true;
                                        }

                                    }
                                    else if (isCollection)
                                    {
                                        List<object> lst = new List<object>(); // this will hold the actual data items
                                        Type itemType = GetCollectionItemType(prop.PropertyType);

                                        if (collectionAttrInst != null && collectionAttrInst.SerializationType == YAXCollectionSerializationTypes.Serially)
                                        {
                                            #region what if the collection was serialized serially
                                            char[] seps = collectionAttrInst.SeparateBy.ToCharArray();

                                            // can white space characters be added to the separators?
                                            if (collectionAttrInst == null || collectionAttrInst.IsWhiteSpaceSeparator)
                                                seps = seps.Union(new char[] { ' ', '\t', '\r', '\n' }).ToArray();

                                            string[] items = elemValue.Split(seps, StringSplitOptions.RemoveEmptyEntries);

                                            foreach (string wordItem in items)
                                            {
                                                try
                                                {
                                                    lst.Add(ConvertBasicType(wordItem, itemType));
                                                }
                                                catch
                                                {
                                                    OnExceptionOccurred(new YAXBadlyFormedInput(propName, elemValue), m_defaultExceptionType);
                                                    exOccurred = true;
                                                }
                                            }
                                            #endregion
                                        }
                                        else
                                        {
                                            #region what if the collection was serialized recursive
                                            bool isPrimitive = false;
                                            YAXSerializer ser = null;

                                            if (IsBasicType(itemType))
                                            {
                                                isPrimitive = true;
                                            }
                                            else
                                            {
                                                ser = new YAXSerializer(itemType, m_exPolicy, m_defaultExceptionType, m_serializationOption);
                                            }

                                            string eachElemName = GetTypeFriendlyName(itemType);
                                            if (collectionAttrInst != null && collectionAttrInst.EachElementName != null)
                                                eachElemName = collectionAttrInst.EachElementName;

                                            foreach (XElement childElem in tElem.Elements(eachElemName))
                                            {
                                                if (isPrimitive)
                                                {
                                                    try
                                                    {
                                                        lst.Add(ConvertBasicType(childElem.Value, itemType));
                                                    }
                                                    catch
                                                    {
                                                        OnExceptionOccurred(new YAXBadlyFormedInput(childElem.Name.ToString(), childElem.Value), m_defaultExceptionType);
                                                        exOccurred = true;
                                                    }
                                                }
                                                else
                                                {
                                                    lst.Add(ser.DeserializeBase(childElem));
                                                }
                                            }

                                            if (ser != null)
                                                m_parsingErrors.AddRange(ser.ParsingErrors);
                                            #endregion
                                        } // end of else if (!isCollection)

                                        // Now what should I do with the filled list: lst
                                        Type dicKeyType, dicValueType, enumType;
                                        Type auxColTypeToCreate;
                                        Type colTypeToCreate;
                                        Type[] typeArgs;
                                        if (IsArray(prop.PropertyType))
                                        {
                                            #region The collection is array
                                            auxColTypeToCreate = typeof(List<>);
                                            typeArgs = new Type[] { itemType };
                                            colTypeToCreate = auxColTypeToCreate.MakeGenericType(typeArgs);
                                            object genArObj = Activator.CreateInstance(colTypeToCreate);

                                            foreach (object lstItem in lst)
                                            {
                                                try
                                                {
                                                    colTypeToCreate.InvokeMember("Add", BindingFlags.InvokeMethod, null, genArObj, new object[] { lstItem });
                                                }
                                                catch
                                                {
                                                    OnExceptionOccurred(new YAXCannotAddObjectToCollection(propName, lstItem), m_defaultExceptionType);
                                                    exOccurred = true;
                                                }
                                            }

                                            try
                                            {
                                                prop.SetValue(o,
                                                    colTypeToCreate.InvokeMember("ToArray", BindingFlags.InvokeMethod, null, genArObj, new object[0]),
                                                    null);
                                            }
                                            catch
                                            {
                                                OnExceptionOccurred(new YAXPropertyCannotBeAssignedTo(propName), m_defaultExceptionType);
                                                exOccurred = true;
                                            }
                                            #endregion
                                        }
                                        else if (IsDictionary(prop.PropertyType, out dicKeyType, out dicValueType))
                                        {
                                            #region The collection is a Dictionary
                                            object dic = prop.PropertyType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, null, null, new object[0]);

                                            object key, value;
                                            foreach (object lstItem in lst)
                                            {
                                                key = itemType.GetProperty("Key").GetValue(lstItem, null);
                                                value = itemType.GetProperty("Value").GetValue(lstItem, null);

                                                try
                                                {
                                                    prop.PropertyType.InvokeMember("Add", BindingFlags.InvokeMethod, null, dic, new object[] { key, value });
                                                }
                                                catch
                                                {
                                                    OnExceptionOccurred(new YAXCannotAddObjectToCollection(propName, lstItem), m_defaultExceptionType);
                                                    exOccurred = true;
                                                }
                                            }

                                            try
                                            {
                                                prop.SetValue(o, dic, null);
                                            }
                                            catch
                                            {
                                                OnExceptionOccurred(new YAXPropertyCannotBeAssignedTo(propName), m_defaultExceptionType);
                                                exOccurred = true;
                                            }
                                            #endregion
                                        }
                                        else if (IsICollection(prop.PropertyType, out enumType))
                                        {
                                            #region The collection is an ICollection e.g. HashSet
                                            object col = prop.PropertyType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, null, null, new object[0]);

                                            foreach (object lstItem in lst)
                                            {
                                                try
                                                {
                                                    prop.PropertyType.InvokeMember("Add", BindingFlags.InvokeMethod, null, col, new object[] { lstItem });
                                                }
                                                catch
                                                {
                                                    OnExceptionOccurred(new YAXCannotAddObjectToCollection(propName, lstItem), m_defaultExceptionType);
                                                    exOccurred = true;

                                                }
                                            }

                                            try
                                            {
                                                prop.SetValue(o, col, null);
                                            }
                                            catch
                                            {
                                                OnExceptionOccurred(new YAXPropertyCannotBeAssignedTo(propName), m_defaultExceptionType);
                                                exOccurred = true;

                                            }
                                            #endregion
                                        }
                                        else if (IsEnumerable(prop.PropertyType, out enumType)) // this MUST always be the last "else if"
                                        {
                                            #region The collection is an IEnumerable
                                            auxColTypeToCreate = typeof(List<>);
                                            typeArgs = new Type[] { enumType };
                                            colTypeToCreate = auxColTypeToCreate.MakeGenericType(typeArgs);
                                            object genArObj = Activator.CreateInstance(colTypeToCreate);

                                            foreach (object lstItem in lst)
                                            {
                                                try
                                                {
                                                    colTypeToCreate.InvokeMember("Add", BindingFlags.InvokeMethod, null, genArObj, new object[] { lstItem });
                                                }
                                                catch
                                                {
                                                    OnExceptionOccurred(new YAXCannotAddObjectToCollection(propName, lstItem), m_defaultExceptionType);
                                                    exOccurred = true;

                                                }
                                            }

                                            try
                                            {
                                                prop.SetValue(o, genArObj, null);
                                            }
                                            catch
                                            {
                                                OnExceptionOccurred(new YAXPropertyCannotBeAssignedTo(propName), m_defaultExceptionType);
                                                exOccurred = true;

                                            }
                                            #endregion
                                        }
                                    } // end of if (isCollection)
                                    else
                                    {
                                        YAXSerializer ser = new YAXSerializer(prop.PropertyType, m_exPolicy, m_defaultExceptionType, m_serializationOption);
                                        convertedObj = ser.DeserializeBase(tElem);
                                        m_parsingErrors.AddRange(ser.ParsingErrors);

                                        try
                                        {
                                            prop.SetValue(o, convertedObj, null);
                                        }
                                        catch
                                        {
                                            OnExceptionOccurred(new YAXPropertyCannotBeAssignedTo(propName), m_defaultExceptionType);
                                            exOccurred = true;
                                        }
                                    } // end of else if(isCollection)
                                } // end of else if prop is not primitive
                            } // end of else if prop type is not string
                        } // end of else if no exception occured
                        #endregion
                    } // end if(!dontSerialize)
                } // end if(prop.CanWrite)
            } // end of foreach property loop

            return o;
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer
Australia Australia
A software designer and developer

Comments and Discussions