using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
namespace Developpez.Dotnet.Xml
{
///
/// Fournit des méthodes pour convertir en XML n'importe quel objet, même anonyme
///
public static class XmlDumper
{
///
/// Renvoie un élément XML avec le nom spécifié représentant l'objet passé en paramètre
///
///L'objet à convertir en XML
///Nom de l'élément XML racine
///Un élément XML représentant l'objet passé en paramètre
/// Les propriétés de type primitif sont représentées par des attributs, celles de type complexe par des sous-éléments.
/// Limitations:
///
/// -
/// Seules les propriétés publiques sont prises en compte
///
/// -
/// Les cycles ne sont pas détectés, donc si l'objet à convertir comporte des cycles, il se produira une StackOverflowException
///
/// -
/// Les propriétés de type Delegate ne sont pas prises en compte
///
///
///
public static XElement ToXml(object obj, string elementName)
{
if (obj == null || obj is Delegate)
return null;
if (IsSimpleType(obj.GetType()))
return new XElement(elementName, obj);
if (obj is IEnumerable)
return (XElement)GetXmlObject(obj, elementName);
var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var xmlProperties =
from p in properties
let name = XmlConvert.EncodeName(p.Name)
let value = GetPropertyValue(obj, p)
let xmlProperty = GetXmlObject(value, name)
where xmlProperty != null
select xmlProperty;
return new XElement(elementName, xmlProperties);
}
///
/// Renvoie un élément XML représentant l'objet passé en paramètre
///
///L'objet à convertir en XML
///Un élément XML représentant l'objet passé en paramètre
/// Les propriétés de type primitif sont représentées par des attributs, celles de type complexe par des sous-éléments.
/// Limitations:
///
/// -
/// Seules les propriétés publiques sont prises en compte
///
/// -
/// Les cycles ne sont pas détectés, donc si l'objet à convertir comporte des cycles, il se produira une StackOverflowException
///
/// -
/// Les propriétés de type Delegate ne sont pas prises en compte
///
///
///
public static XElement ToXml(object obj)
{
if (obj == null)
return null;
return ToXml(obj, GetXmlTypeName(obj.GetType()));
}
private static XObject GetXmlObject(object obj, string name)
{
if (obj == null || obj is Delegate)
return null;
if (IsSimpleType(obj.GetType()))
return new XAttribute(name, obj);
if (obj is IEnumerable)
{
var xmlItems =
from object item in (IEnumerable)obj
select item == null
? new XElement("Null")
: ToXml(item, GetXmlTypeName(item.GetType()));
return new XElement(name, xmlItems);
}
return ToXml(obj, name);
}
private static string GetXmlTypeName(Type type)
{
if (type.Name.StartsWith("<>")) // Anonymous type
return "Object";
if (!IsSimpleType(type) && typeof(IEnumerable).IsAssignableFrom(type))
{
Type genericEnumerable = type.GetInterface("IEnumerable`1");
if (genericEnumerable != null)
{
Type itemType = genericEnumerable.GetGenericArguments()[0];
return "ArrayOf" + GetXmlTypeName(itemType);
}
return "ArrayOfObject";
}
string baseName = type.Name.From(0).To("`", false);
return XmlConvert.EncodeName(baseName);
}
private static bool IsSimpleType(Type t)
{
return t.IsPrimitive
|| t.IsEnum
|| t == typeof(string)
|| t == typeof(decimal)
|| t == typeof(Guid)
|| t == typeof(DateTime);
}
private delegate object Getter(object instance);
private static readonly Dictionary _getters = new Dictionary();
private static object GetPropertyValue(object obj, PropertyInfo prop)
{
Getter getter;
if (!_getters.TryGetValue(prop.MetadataToken, out getter))
{
Type objType = obj.GetType();
ParameterExpression prm = Expression.Parameter(typeof(object), "obj");
Expression body = Expression.Property(
Expression.Convert(prm, objType),
prop);
// Box value types
if (prop.PropertyType.IsValueType)
body = Expression.Convert(body, typeof(object));
Expression expr = Expression.Lambda(body, prm);
getter = expr.Compile();
_getters[prop.MetadataToken] = getter;
}
return getter(obj);
}
}
}