Classe xmlmapper

Description

Pour les besoin d'un projet, j'avais besoin de mapper un XML très complexe sur un objet C#.
Le code étant fastidieux à écrire, j'ai décidé de réfléchir (au sens C# du terme).
J'ai donc créé une classe qui utilise la reflexion et des attributs personnalisés pour mapper les valeurs contenues dans le XML sur mon objet C#.
Je vous livre le code de la classe, et un exemple d'utilisation.

Source / Exemple :


using System;
using System.Collections;
using System.Reflection;
using System.Xml;

namespace Tools
{
	/// <summary>
	/// Classe de mapping de fichiers XML sur objets
	/// </summary>
	public class XmlMapper : IXmlMapper
	{
		protected XmlNode MapNode;

		#region Mapper d'objet
		/// <summary>
		/// Lit un fichier XML et le mappe à l'objet
		/// </summary>
		/// <param name="fileName">Nom du fichier à lire</param>
		public void Load ( string fileName )
		{
			Load(fileName, this);
		}

		/// <summary>
		/// Lit la chaîne XML et la mappe à l'objet
		/// </summary>
		/// <param name="xml">Chaîne XML</param>
		public void LoadXml ( string xml )
		{
			LoadXml(xml, this);
		}

		/// <summary>
		/// Lit l'objet XML et le mappe à l'objet
		/// </summary>
		/// <param name="xd">Objet XML</param>
		public void Load ( XmlNode xn )
		{
			Load(xn, this);
		}
		#endregion

		#region Procédures statiques
		/// <summary>
		/// Type du xmlMapper, pour ne pas avoir à faire des typeof à tout bout de champs
		/// </summary>
		private static Type XmlMapperType = typeof(XmlMapper);

		/// <summary>
		/// Type du XmlMapAttributeType, pour ne pas avoir à faire des typeof à tout bout de champs
		/// </summary>
		private static Type XmlMapAttributeType = typeof(XmlMapAttribute);

		/// <summary>
		/// Lit un fichier XML et le mappe à l'objet
		/// </summary>
		/// <param name="fileName">Nom du fichier à lire</param>
		/// <param name="obj">Objet à mapper</param>
		public static void Load ( string filename, object obj )
		{
			XmlDocument xd = new XmlDocument();
			xd.Load(filename);
			Load(xd, obj);
		}

		/// <summary>
		/// Lit la chaîne XML et la mappe à l'objet
		/// </summary>
		/// <param name="xml">Chaîne XML</param>
		/// <param name="obj">Objet à mapper</param>
		public static void LoadXml ( string xml, object obj )
		{
			XmlDocument xd = new XmlDocument();
			xd.LoadXml(xml);
			Load(xd, obj);
		}

		/// <summary>
		/// Lit l'objet XML et le mappe à l'objet
		/// </summary>
		/// <param name="xd">Objet XML</param>
		/// <param name="obj">Objet à mapper</param>
		public static void Load ( XmlNode xn, object obj )
		{
			XmlDocument xd = xn is XmlDocument ? (XmlDocument)xn : xn.OwnerDocument;
			XmlNamespaceManager xns = new XmlNamespaceManager(xd.NameTable);

			Type ObjectType = obj.GetType();
			Type XmlMapNameSpaceType = typeof(XmlMapNameSpaceAttribute);
			foreach (XmlMapNameSpaceAttribute xmlMapNamespaceAttribute in ObjectType.GetCustomAttributes(XmlMapNameSpaceType, true)) {
				xns.AddNamespace(xmlMapNamespaceAttribute.Prefix, xmlMapNamespaceAttribute.Namespace);
			}

			Load(xn, obj, xns);
		}

		/// <summary>
		/// Lit l'objet XML et le mappe à l'objet
		/// </summary>
		/// <param name="xn">Objet XML</param>
		/// <param name="obj">Objet à mapper</param>
		/// <param name="xns">Gestionnaire d'espace de noms XML du mappeur</param>
		public static void Load ( XmlNode xn, object obj, XmlNamespaceManager xns )
		{
			Type ObjectType = obj.GetType();

			if (obj is XmlMapper) {
				((XmlMapper)obj).MapNode = xn;
			}

			XmlNode context = xn;
			foreach (XmlMapAttribute xmlMap in ObjectType.GetCustomAttributes(XmlMapAttributeType, true)) {
				context = xn.SelectSingleNode(xmlMap.XPath, xns);
				if (context == null) return;
			}

			foreach (PropertyInfo property in ObjectType.GetProperties()) {
				MapProperty(obj, xns, context, property);
			}
		}

		/// <summary>
		/// Mappe le noued XML sur la propriété
		/// </summary>
		/// <param name="obj">objet contenant la propriété</param>
		/// <param name="xns">Gestionnaire d'espace de nom</param>
		/// <param name="context">Noeud XML courrant</param>
		/// <param name="property">Propriété à mapper</param>
		private static void MapProperty ( object obj, XmlNamespaceManager xns, XmlNode context, PropertyInfo property )
		{
			XmlMapAttribute xmlMap = (XmlMapAttribute)property.GetCustomAttributes(XmlMapAttributeType, true)[0];
			Type propertyType = property.PropertyType;
			if (propertyType.IsArray) {
				//Cas du tableau
				MapArray(obj, xns, context, property, xmlMap, propertyType);
			} else if (propertyType.IsGenericType && Implements(propertyType, typeof(IDictionary))) {
				//Cas du dictionnaire fortement typé
				Type[] genericType = propertyType.GetGenericArguments();
				Type keyType = genericType[0];
				Type valueType = genericType[1];
				MapDictionary(obj, xns, context, property, xmlMap, keyType, valueType);
			} else if (Implements(propertyType, typeof(IDictionary))) {
				//Cas du dictionnaire non typé
				MapDictionary(obj, xns, context, property, xmlMap, typeof(string), typeof(string));
			} else if (propertyType.IsGenericType && Implements(propertyType, typeof(IList))) {
				//Cas de la liste fortement typée
				Type[] genericType = propertyType.GetGenericArguments();
				Type valueType = genericType[0];
				MapList(obj, xns, context, property, xmlMap, valueType);
			} else if (Implements(propertyType, typeof(IList))) {
				//Cas de la liste faiblement typée
				MapList(obj, xns, context, property, xmlMap, typeof(string));
			} else {
				//Cas de lobjet simple
				MapSimpleValue(context, xns, obj, xmlMap, property, propertyType);
			}
		}

		/// <summary>
		/// Vérifie sur le type implémente l'interface spécifiée
		/// </summary>
		/// <param name="t">Type à tester</param>
		/// <param name="interfaceType">Interface à vérifier</param>
		/// <returns>Vrai si le type implémente l'interface</returns>
		private static bool Implements (Type t,  Type interfaceType )
		{
			foreach (Type implements in t.GetInterfaces()) {
				if (implements == interfaceType) return true;
			}
			return false;
		}

		/// <summary>
		/// Mappe une série de noeud sur un dictionaire
		/// </summary>
		/// <param name="obj">Objet contant la propriété</param>
		/// <param name="xns">Gestionnaire d'espace de noms XML du mappeur</param>
		/// <param name="context">Contexte xml</param>
		/// <param name="property">Descritpteur de la propriété</param>
		/// <param name="xmlMap">Mappeur XMl</param>
		/// <param name="KeyType">Type de la clef</param>
		/// <param name="ValueType">Type des valeurs</param>
		private static void MapDictionary ( object obj, XmlNamespaceManager xns, XmlNode context, PropertyInfo property, XmlMapAttribute xmlMap, Type KeyType, Type ValueType )
		{
			XmlNodeList valueNodes = context.SelectNodes(xmlMap.XPath, xns);
			IDictionary dictionary = (IDictionary)property.GetGetMethod().Invoke(obj, new object[0]);
			FillDictionaryFromNodesValues(KeyType, ValueType, valueNodes, xns, xmlMap, dictionary);
		}

		/// <summary>
		/// Mappe une série de noeud sur une liste
		/// </summary>
		/// <param name="obj">Objet contant la propriété</param>
		/// <param name="xns">Gestionnaire d'espace de noms XML du mappeur</param>
		/// <param name="context">Contexte xml</param>
		/// <param name="property">Descritpteur de la propriété</param>
		/// <param name="xmlMap">Mappeur XMl</param>
		/// <param name="ValueType">Type des valeurs</param>
		private static void MapList ( object obj, XmlNamespaceManager xns, XmlNode context, PropertyInfo property, XmlMapAttribute xmlMap, Type ValueType )
		{
			XmlNodeList valueNodes = context.SelectNodes(xmlMap.XPath, xns);
			IList list = (IList)property.GetGetMethod().Invoke(obj, new object[0]);
			FillListFromNodesValues(ValueType, valueNodes, xns, xmlMap, list);
		}

		/// <summary>
		/// Mappe une série de noeuds sur un tableau
		/// </summary>
		/// <param name="obj">Objet contant la propriété</param>
		/// <param name="context">Contexte xml</param>
		/// <param name="property">Descritpteur de la propriété</param>
		/// <param name="xmlMap">Mappeur XMl</param>
		/// <param name="propertyType">Type de la propriété</param>
		private static void MapArray ( object obj, XmlNamespaceManager xns, XmlNode context, PropertyInfo property, XmlMapAttribute xmlMap, Type propertyType )
		{
			XmlNodeList valueNodes = context.SelectNodes(xmlMap.XPath, xns);
			if (valueNodes.Count == 0) {
				if (xmlMap.Mandatory) throw new Exception(xmlMap.ErrorMessage);
			} else {
				property.SetValue(obj, GetNodesValues(propertyType.GetElementType(), valueNodes, xns), null);
			}
		}

		/// <summary>
		/// Mappe une valeur simple (entier, chaîne, date...)
		/// </summary>
		/// <param name="xn">Noeud de contexte</param>
		/// <param name="xmlMap">Description du mapping</param>
		/// <param name="Value">Valeur à mapper</param>
		private static void MapSimpleValue ( XmlNode xn, XmlNamespaceManager xns, object obj, XmlMapAttribute xmlMap, PropertyInfo property, Type PropertyType )
		{
			XmlNode valueNode = xn.SelectSingleNode(xmlMap.XPath, xns);
			if (valueNode == null) {
				if (xmlMap.Mandatory) throw new Exception(xmlMap.ErrorMessage);
			} else {
				object Value = GetNodeValue(PropertyType, valueNode, xns);
				property.SetValue(obj, Value, null);
			}
		}

		/// <summary>
		/// Lit la valeur du noeud
		/// </summary>
		/// <param name="valueType">Type de valeur</param>
		/// <param name="valueNode">Noeud contenant la valeur</param>
		/// <returns></returns>
		private static object GetNodeValue ( Type valueType, XmlNode valueNode, XmlNamespaceManager xns )
		{
			if (Implements(valueType,typeof (IXmlMapper))) {
				//si le noeud est un mappeur, mappe le noeud au niveau courant
				IXmlMapper Value = (IXmlMapper)valueType.GetConstructor(new Type[0]).Invoke(new object[0]);
				Load(valueNode, Value, xns);
				return Value;
			} else {
				//sinon, lit la valeur et tente de la convertir
				string value;
				if (valueNode is XmlElement) {
					value = valueNode.InnerText;
				} else {
					value = valueNode.Value;
				}

				object Value = Convert.ChangeType(value, valueType);
				return Value;
			}
		}

		/// <summary>
		/// Mappe un tableau (entier, chaîne, date...)
		/// </summary>
		/// <param name="valueType">Type de valeur</param>
		/// <param name="valueNode">Liste de noeuds contenants les valeurs</param>
		private static Array GetNodesValues ( Type valueType, XmlNodeList valuesNodes, XmlNamespaceManager xns )
		{
			Array returnValue = Array.CreateInstance(valueType, valuesNodes.Count);

			for (int i = 0; i < valuesNodes.Count; i++) {
				returnValue.SetValue(GetNodeValue(valueType, valuesNodes[i], xns), i);
			}

			return returnValue;
		}

		/// <summary>
		/// Mappe un tableau (entier, chaîne, date...)
		/// </summary>
		/// <param name="valueType">Type de valeur</param>
		/// <param name="valueNode">Liste de noeuds contenants les valeurs</param>
		private static void FillDictionaryFromNodesValues ( Type keyType, Type valueType, XmlNodeList valuesNodes, XmlNamespaceManager xns, XmlMapAttribute xmlMap, IDictionary dictionary )
		{
			for (int i = 0; i < valuesNodes.Count; i++) {
				XmlNode context = valuesNodes[i];
				XmlNode KeyNode = context.SelectSingleNode(xmlMap.XPathKey, xns);
				XmlNode ValueNode = context.SelectSingleNode(xmlMap.XPathValue, xns);

				dictionary.Add(
					GetNodeValue(keyType, KeyNode, xns),
					GetNodeValue(valueType, ValueNode, xns));
			}
		}

		/// <summary>
		/// Mappe une collection (entier, chaîne, date...)
		/// </summary>
		/// <param name="valueType">Type de valeur</param>
		/// <param name="valueNode">Liste de noeuds contenants les valeurs</param>
		private static void FillListFromNodesValues ( Type valueType, XmlNodeList valuesNodes, XmlNamespaceManager xns, XmlMapAttribute xmlMap, IList list )
		{
			for (int i = 0; i < valuesNodes.Count; i++) {
				XmlNode context = valuesNodes[i];
				XmlNode ValueNode = context.SelectSingleNode(xmlMap.XPathValue, xns);

				list.Add(GetNodeValue(valueType, ValueNode, xns));
			}
		}

		#endregion
	}

	/// <summary>
	/// Déclaration des namespace pour le mapping XML
	/// </summary>
	class XmlMapNameSpaceAttribute : Attribute
	{
		/// <summary>
		/// Préfixe de recherche
		/// </summary>
		public string Prefix { get; private set; }

		/// <summary>
		/// Namespace associé
		/// </summary>
		public string Namespace { get; private set; }

		/// <summary>
		/// Déclare un namespace pour le mapping
		/// </summary>
		/// <param name="Prefix">Préfixe de recherche</param>
		/// <param name="Namespace">Namespace associé</param>
		public XmlMapNameSpaceAttribute ( string Prefix, string Namespace )
		{
			this.Prefix = Prefix;
			this.Namespace = Namespace;
		}
	}

	/// <summary>
	/// Déclaration des chaînes de recherche pour le mapping
	/// </summary>
	class XmlMapAttribute : Attribute
	{
		/// <summary>
		/// XPath de recherche de la valeur
		/// </summary>
		public string XPath { get; private set; }
		/// <summary>
		/// Dans le cas des valeurs simples, indique si la valeur est obligatoire
		/// </summary>
		public bool Mandatory { get; set; }
		/// <summary>
		/// Message d'erreur si la valeur est absente et obligatoire
		/// </summary>
		public string ErrorMessage { get; set; }

		/// <summary>
		/// Chemin relatif vers la clef pour remplir les collections
		/// </summary>
		public string XPathKey { get; private set; }
		/// <summary>
		/// chemin relatif vers la valeur pour remplir les collections
		/// </summary>
		public string XPathValue { get; private set; }

		/// <summary>
		/// Ajoute un mapping d'attribut
		/// </summary>
		/// <param name="XPath">Chemin de recherche</param>
		public XmlMapAttribute ( string XPath )
		{
			this.XPath = XPath;
			this.ErrorMessage = "Value is mandatory";
			this.Mandatory = false;
		}

		/// <summary>
		/// Ajoute un mapping d'attribut
		/// </summary>
		/// <param name="XPath">Chemin de recherche</param>
		/// <param name="XPathKey">Chemin relatif vers la clef pour remplir les collections</param>
		/// <param name="XPathValue">chemin relatif vers la valeur pour remplir les collections</param>
		public XmlMapAttribute ( string XPath, string XPathValue )
			: this(XPath)
		{
			this.XPathValue = XPathValue;
		}

		/// <summary>
		/// Ajoute un mapping d'attribut
		/// </summary>
		/// <param name="XPath">Chemin de recherche</param>
		/// <param name="XPathKey">Chemin relatif vers la clef pour remplir les collections</param>
		/// <param name="XPathValue">chemin relatif vers la valeur pour remplir les collections</param>
		public XmlMapAttribute ( string XPath, string XPathKey, string XPathValue )
			: this(XPath)
		{
			this.XPathKey = XPathKey;
			this.XPathValue = XPathValue;
		}
	}

	/// <summary>
	/// Interface pour déclarer les types mappables en XML
	/// </summary>
	public interface IXmlMapper { }

}

Conclusion :


Pour l'instant, la classe ne marche que dans le sens lecture. Mais je compte bien le rendre bidirectionnel... mais pas tout de suite.
Je compte dans un temps plus court, mapper les propriétés protégées et privées.

En principe tout fonctionne correctement, mais si vous trouvez un bug ou une amélioration, je suis preneur de tous commentaires.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.