Skip to content

Instantly share code, notes, and snippets.

@huoxudong125
Created September 2, 2015 02:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save huoxudong125/49684802e21ae6b2c121 to your computer and use it in GitHub Desktop.
Save huoxudong125/49684802e21ae6b2c121 to your computer and use it in GitHub Desktop.
A C# helper to read and write XML from and to objects
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml.Linq;
namespace Nwazet.Commerce.Helpers {
public static class XmlHelper {
/// <summary>
/// Like Add, but chainable.
/// </summary>
/// <param name="el">The parent element.</param>
/// <param name="children">The elements to add.</param>
/// <returns>Itself</returns>
public static XElement AddEl(this XElement el, params XElement[] children) {
el.Add(children.Cast<object>());
return el;
}
/// <summary>
/// Gets the string value of an attribute, and null if the attribute doesn't exist.
/// </summary>
/// <param name="el">The element.</param>
/// <param name="name">The name of the attribute.</param>
/// <returns>The string value of the attribute if it exists, null otherwise.</returns>
public static string Attr(this XElement el, string name) {
var attr = el.Attribute(name);
return attr == null ? null : attr.Value;
}
/// <summary>
/// Gets a typed value from an attribute.
/// </summary>
/// <typeparam name="T">The type of the value</typeparam>
/// <param name="el">The element.</param>
/// <param name="name">The name of the attribute.</param>
/// <returns>The attribute value</returns>
public static T Attr<T>(this XElement el, string name) {
var attr = el.Attribute(name);
var type = typeof (T);
if (attr == null ||
((!type.IsValueType || Nullable.GetUnderlyingType(type) != null) && type != typeof(string) &&
"null".Equals(attr.Value, StringComparison.Ordinal))) {
return default(T);
}
if (type == typeof (string)) {
return (T)(object)attr.Value;
}
if (type == typeof (int)) {
return (T)(object)(int) attr; // Pretty, eh? OK, if you're so smart, find a better way. I'm waiting. Seriously, I'd love to not do this.
}
if (type == typeof (bool)) {
return (T)(object)(bool)attr;
}
if (type == typeof (DateTime)) {
return (T)(object)(DateTime)attr;
}
if (type == typeof (double)) {
return (T)(object)(double)attr;
}
if (type == typeof (float)) {
return (T)(object)(float)attr;
}
if (type == typeof (decimal)) {
return (T)(object)(decimal)attr;
}
if (type == typeof (int?)) {
return (T)(object)(int?)attr;
}
if (type == typeof (bool?)) {
return (T)(object)(bool?)attr;
}
if (type == typeof (DateTime?)) {
return (T)(object)(DateTime?)attr;
}
if (type == typeof (double?)) {
return (T)(object)(double?)attr;
}
if (type == typeof (float?)) {
return (T)(object)(float?)attr;
}
if (type == typeof (decimal?)) {
return (T)(object)(decimal?)attr;
}
throw new InvalidCastException(String.Format("Couldn't find how to deserialize to type {0}.", type.Name));
}
/// <summary>
/// Sets an attribute value. This is chainable.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="el">The element.</param>
/// <param name="name">The attribute name.</param>
/// <param name="value">The value to set.</param>
/// <returns>Itself</returns>
public static XElement Attr<T>(this XElement el, string name, T value) {
el.SetAttributeValue(name, value);
return el;
}
/// <summary>
/// Returns the text contents of a child element.
/// </summary>
/// <param name="el">The parent element.</param>
/// <param name="name">The name of the child element.</param>
/// <returns>The text for the child element, and null if it doesn't exist.</returns>
public static string El(this XElement el, string name) {
var childElement = el.Element(name);
return childElement == null ? null : childElement.Value;
}
/// <summary>
/// Creates and sets the value of a child element. This is chainable.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="el">The parent element.</param>
/// <param name="name">The name of the child element.</param>
/// <param name="value">The value to set.</param>
/// <returns>Itself</returns>
public static XElement El<T>(this XElement el, string name, T value) {
el.SetElementValue(name, value);
return el;
}
/// <summary>
/// Sets a property value from an attribute of the same name.
/// </summary>
/// <typeparam name="TTarget">The type of the target object.</typeparam>
/// <typeparam name="TProperty">The type of the target property</typeparam>
/// <param name="el">The element.</param>
/// <param name="target">The target object.</param>
/// <param name="targetExpression">The property expression.</param>
/// <returns>Itself</returns>
public static XElement FromAttr<TTarget, TProperty>(this XElement el, TTarget target,
Expression<Func<TTarget, TProperty>> targetExpression) {
var propertyInfo = GetPropertyInfo(targetExpression);
var name = propertyInfo.Name;
var attr = el.Attribute(name);
if (attr == null) return el;
propertyInfo.SetValue(target, el.Attr<TProperty>(name), null);
return el;
}
/// <summary>
/// Sets an attribute with the value of a property of the same name.
/// </summary>
/// <typeparam name="TTarget">The type of the object.</typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="el">The element.</param>
/// <param name="target">The object.</param>
/// <param name="targetExpression">The property expression.</param>
/// <returns>Itself</returns>
public static XElement ToAttr<TTarget, TProperty>(this XElement el, TTarget target,
Expression<Func<TTarget, TProperty>> targetExpression) {
var propertyInfo = GetPropertyInfo(targetExpression);
var name = propertyInfo.Name;
var val = propertyInfo.GetValue(target, null);
if (typeof (TProperty) == typeof (string)) {
el.Attr(name, (string) val);
}
else if (val == null) {
el.Attr(name, "null");
}
else if (typeof (TProperty) == typeof (int)) {
el.Attr(name, (int) val);
}
else if (typeof (TProperty) == typeof (bool)) {
el.Attr(name, (bool) val);
}
else if (typeof (TProperty) == typeof (DateTime)) {
el.Attr(name, (DateTime) val);
}
else if (typeof (TProperty) == typeof (double)) {
el.Attr(name, (double) val);
}
else if (typeof (TProperty) == typeof (float)) {
el.Attr(name, (float) val);
}
else if (typeof (TProperty) == typeof (decimal)) {
el.Attr(name, (decimal) val);
}
else if (typeof (TProperty) == typeof (int?)) {
el.Attr(name, (int?) val);
}
else if (typeof (TProperty) == typeof (bool?)) {
el.Attr(name, (bool?) val);
}
else if (typeof (TProperty) == typeof (DateTime?)) {
el.Attr(name, (DateTime?) val);
}
else if (typeof (TProperty) == typeof (double?)) {
el.Attr(name, (double?) val);
}
else if (typeof (TProperty) == typeof (float?)) {
el.Attr(name, (float?) val);
}
else if (typeof (TProperty) == typeof (decimal?)) {
el.Attr(name, (decimal?) val);
}
return el;
}
public static PropertyInfo GetPropertyInfo<TContext, TProperty>(Expression<Func<TContext, TProperty>> expression) {
var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
throw new InvalidOperationException("Expression is not a member expression.");
var propertyInfo = memberExpression.Member as PropertyInfo;
if (propertyInfo == null) throw new InvalidOperationException("Expression is not for a property.");
return propertyInfo;
}
/// <summary>
/// Gives context to an XElement, enabling chained property operations.
/// </summary>
/// <typeparam name="TContext">The type of the context.</typeparam>
/// <param name="el">The element.</param>
/// <param name="context">The context.</param>
/// <returns>The element with context.</returns>
public static XElementWithContext<TContext> With<TContext>(this XElement el, TContext context) {
return new XElementWithContext<TContext>(el, context);
}
public class XElementWithContext<TContext> {
public XElementWithContext(XElement element, TContext context) {
Element = element;
Context = context;
}
public XElement Element { get; private set; }
public TContext Context { get; private set; }
public static implicit operator XElement(XElementWithContext<TContext> elementWithContext) {
return elementWithContext.Element;
}
/// <summary>
/// Replaces the current context with a new one, enabling chained action on different objects.
/// </summary>
/// <typeparam name="TNewContext">The type of the new context.</typeparam>
/// <param name="context">The new context.</param>
/// <returns>A new XElementWithContext, that has the new context.</returns>
public XElementWithContext<TNewContext> With<TNewContext>(TNewContext context) {
return new XElementWithContext<TNewContext>(Element, context);
}
/// <summary>
/// Sets the value of a context property as an attribute of the same name on the element.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="targetExpression">The property expression.</param>
/// <returns>Itself</returns>
public XElementWithContext<TContext> ToAttr<TProperty>(
Expression<Func<TContext, TProperty>> targetExpression) {
Element.ToAttr(Context, targetExpression);
return this;
}
/// <summary>
/// Gets an attribute on the element and sets the property of the same name on the context with its value.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="targetExpression">The property expression.</param>
/// <returns>Itself</returns>
public XElementWithContext<TContext> FromAttr<TProperty>(
Expression<Func<TContext, TProperty>> targetExpression) {
Element.FromAttr(Context, targetExpression);
return this;
}
/// <summary>
/// Evaluates an attribute from an expression.
/// It's a nice strongly-typed way to read attributes.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="expression">The property expression.</param>
/// <returns>The attribute, ready to be cast.</returns>
public TProperty Attr<TProperty>(Expression<Func<TContext, TProperty>> expression) {
var propertyInfo = GetPropertyInfo(expression);
var name = propertyInfo.Name;
return Element.Attr<TProperty>(name);
}
}
}
}
using System;
using System.Xml.Linq;
using NUnit.Framework;
using Nwazet.Commerce.Helpers;
namespace Nwazet.Commerce.Tests {
[TestFixture]
public class XmlHelperTests {
[Test]
public void StringToAttribute() {
var el = new XElement("data");
el.Attr("foo", "bar");
Assert.That(el.Attribute("foo").Value, Is.EqualTo("bar"));
}
[Test]
public void IntToAttribute() {
var el = new XElement("data");
el.Attr("foo", 42);
Assert.That(el.Attribute("foo").Value, Is.EqualTo("42"));
}
[Test]
public void BoolToAttribute() {
var el = new XElement("data");
el.Attr("foo", true);
el.Attr("bar", false);
Assert.That(el.Attribute("foo").Value, Is.EqualTo("true"));
Assert.That(el.Attribute("bar").Value, Is.EqualTo("false"));
}
[Test]
public void DateTimeToAttribute() {
var el = new XElement("data");
el.Attr("foo", new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc));
Assert.That(el.Attribute("foo").Value, Is.EqualTo("1970-05-21T13:55:21.934Z"));
}
[Test]
public void DoubleFloatDecimalToAttribute() {
var el = new XElement("data");
el.Attr("double", 12.456D);
el.Attr("float", 12.457F);
el.Attr("decimal", 12.458M);
Assert.That(el.Attribute("double").Value, Is.EqualTo("12.456"));
Assert.That(el.Attribute("float").Value, Is.EqualTo("12.457"));
Assert.That(el.Attribute("decimal").Value, Is.EqualTo("12.458"));
}
[Test]
public void ReadAttribute() {
var el = XElement.Parse("<data foo=\"bar\"/>");
Assert.That(el.Attr("foo"), Is.EqualTo("bar"));
Assert.That(el.Attr("bar"), Is.Null);
}
[Test]
public void SerializeObject() {
var target = new Target {
AString = "foo",
AnInt = 42,
ABoolean = true,
ADate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc),
ADouble = 12.345D,
AFloat = 23.456F,
ADecimal = 34.567M,
ANullableInt = 42,
ANullableBoolean = true,
ANullableDate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc),
ANullableDouble = 12.345D,
ANullableFloat = 23.456F,
ANullableDecimal = 34.567M
};
var el = new XElement("data");
el.With(target)
.ToAttr(t => t.AString)
.ToAttr(t => t.AnInt)
.ToAttr(t => t.ABoolean)
.ToAttr(t => t.ADate)
.ToAttr(t => t.ADouble)
.ToAttr(t => t.AFloat)
.ToAttr(t => t.ADecimal)
.ToAttr(t => t.ANullableInt)
.ToAttr(t => t.ANullableBoolean)
.ToAttr(t => t.ANullableDate)
.ToAttr(t => t.ANullableDouble)
.ToAttr(t => t.ANullableFloat)
.ToAttr(t => t.ANullableDecimal);
Assert.That(el.Attr("AString"), Is.EqualTo("foo"));
Assert.That(el.Attr("AnInt"), Is.EqualTo("42"));
Assert.That(el.Attr("ABoolean"), Is.EqualTo("true"));
Assert.That(el.Attr("ADate"), Is.EqualTo("1970-05-21T13:55:21.934Z"));
Assert.That(el.Attr("ADouble"), Is.EqualTo("12.345"));
Assert.That(el.Attr("AFloat"), Is.EqualTo("23.456"));
Assert.That(el.Attr("ADecimal"), Is.EqualTo("34.567"));
Assert.That(el.Attr("ANullableInt"), Is.EqualTo("42"));
Assert.That(el.Attr("ANullableBoolean"), Is.EqualTo("true"));
Assert.That(el.Attr("ANullableDate"), Is.EqualTo("1970-05-21T13:55:21.934Z"));
Assert.That(el.Attr("ANullableDouble"), Is.EqualTo("12.345"));
Assert.That(el.Attr("ANullableFloat"), Is.EqualTo("23.456"));
Assert.That(el.Attr("ANullableDecimal"), Is.EqualTo("34.567"));
}
[Test]
public void DeSerializeObject() {
var target = new Target();
var el =
XElement.Parse(
"<data AString=\"foo\" AnInt=\"42\" ABoolean=\"true\" " +
"ADate=\"1970-05-21T13:55:21.934Z\" ADouble=\"12.345\" " +
"AFloat=\"23.456\" ADecimal=\"34.567\" " +
"ANullableInt=\"42\" ANullableBoolean=\"true\" " +
"ANullableDate=\"1970-05-21T13:55:21.934Z\" ANullableDouble=\"12.345\" " +
"ANullableFloat=\"23.456\" ANullableDecimal=\"34.567\"/>");
el.With(target)
.FromAttr(t => t.AString)
.FromAttr(t => t.AnInt)
.FromAttr(t => t.ABoolean)
.FromAttr(t => t.ADate)
.FromAttr(t => t.ADouble)
.FromAttr(t => t.AFloat)
.FromAttr(t => t.ADecimal)
.FromAttr(t => t.ANullableInt)
.FromAttr(t => t.ANullableBoolean)
.FromAttr(t => t.ANullableDate)
.FromAttr(t => t.ANullableDouble)
.FromAttr(t => t.ANullableFloat)
.FromAttr(t => t.ANullableDecimal);
Assert.That(target.AString, Is.EqualTo("foo"));
Assert.That(target.AnInt, Is.EqualTo(42));
Assert.That(target.ABoolean, Is.True);
Assert.That(target.ADate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc)));
Assert.That(target.ADouble, Is.EqualTo(12.345D));
Assert.That(target.AFloat, Is.EqualTo(23.456F));
Assert.That(target.ADecimal, Is.EqualTo(34.567M));
Assert.That(target.ANullableInt, Is.EqualTo(42));
Assert.That(target.ANullableBoolean, Is.True);
Assert.That(target.ANullableDate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc)));
Assert.That(target.ANullableDouble, Is.EqualTo(12.345D));
Assert.That(target.ANullableFloat, Is.EqualTo(23.456F));
Assert.That(target.ANullableDecimal, Is.EqualTo(34.567M));
}
[Test]
public void DeSerializeFromMissingAttributeLeavesValueIntact() {
var target = new Target {
AString = "foo",
AnInt = 42,
ABoolean = true,
ADate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc),
ADouble = 12.345D,
AFloat = 23.456F,
ADecimal = 34.567M,
ANullableInt = 42,
ANullableBoolean = true,
ANullableDate = new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc),
ANullableDouble = 12.345D,
ANullableFloat = 23.456F,
ANullableDecimal = 34.567M
};
var el = new XElement("data");
el.With(target)
.FromAttr(t => t.AString)
.FromAttr(t => t.AnInt)
.FromAttr(t => t.ABoolean)
.FromAttr(t => t.ADate)
.FromAttr(t => t.ADouble)
.FromAttr(t => t.AFloat)
.FromAttr(t => t.ADecimal)
.FromAttr(t => t.ANullableInt)
.FromAttr(t => t.ANullableBoolean)
.FromAttr(t => t.ANullableDate)
.FromAttr(t => t.ANullableDouble)
.FromAttr(t => t.ANullableFloat)
.FromAttr(t => t.ANullableDecimal);
Assert.That(target.AString, Is.EqualTo("foo"));
Assert.That(target.AnInt, Is.EqualTo(42));
Assert.That(target.ABoolean, Is.True);
Assert.That(target.ADate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc)));
Assert.That(target.ADouble, Is.EqualTo(12.345D));
Assert.That(target.AFloat, Is.EqualTo(23.456F));
Assert.That(target.ADecimal, Is.EqualTo(34.567M));
Assert.That(target.ANullableInt, Is.EqualTo(42));
Assert.That(target.ANullableBoolean, Is.True);
Assert.That(target.ANullableDate, Is.EqualTo(new DateTime(1970, 5, 21, 13, 55, 21, 934, DateTimeKind.Utc)));
Assert.That(target.ANullableDouble, Is.EqualTo(12.345D));
Assert.That(target.ANullableFloat, Is.EqualTo(23.456F));
Assert.That(target.ANullableDecimal, Is.EqualTo(34.567M));
}
[Test]
public void NullSerializes() {
var target = new Target();
var el = new XElement("data");
el.With(target)
.ToAttr(t => t.AString)
.ToAttr(t => t.ANullableInt)
.ToAttr(t => t.ANullableBoolean)
.ToAttr(t => t.ANullableDate)
.ToAttr(t => t.ANullableDouble)
.ToAttr(t => t.ANullableFloat)
.ToAttr(t => t.ANullableDecimal);
Assert.That(el.Attr("AString"), Is.Null);
Assert.That(el.Attr("ANullableInt"), Is.EqualTo("null"));
Assert.That(el.Attr("ANullableBoolean"), Is.EqualTo("null"));
Assert.That(el.Attr("ANullableDate"), Is.EqualTo("null"));
Assert.That(el.Attr("ANullableDouble"), Is.EqualTo("null"));
Assert.That(el.Attr("ANullableFloat"), Is.EqualTo("null"));
Assert.That(el.Attr("ANullableDecimal"), Is.EqualTo("null"));
}
[Test]
public void DeSerializeNull() {
var target = new Target();
var el =
XElement.Parse(
"<data AString=\"null\" ANullableInt=\"null\" ANullableBoolean=\"null\" " +
"ANullableDate=\"null\" ANullableDouble=\"null\" " +
"ANullableFloat=\"null\" ANullableDecimal=\"null\"/>");
el.With(target)
.FromAttr(t => t.AString)
.FromAttr(t => t.ANullableInt)
.FromAttr(t => t.ANullableBoolean)
.FromAttr(t => t.ANullableDate)
.FromAttr(t => t.ANullableDouble)
.FromAttr(t => t.ANullableFloat)
.FromAttr(t => t.ANullableDecimal);
Assert.That(target.AString, Is.EqualTo("null"));
Assert.That(target.ANullableInt, Is.Null);
Assert.That(target.ANullableBoolean, Is.Null);
Assert.That(target.ANullableDate, Is.Null);
Assert.That(target.ANullableDouble, Is.Null);
Assert.That(target.ANullableFloat, Is.Null);
Assert.That(target.ANullableDecimal, Is.Null);
}
private class Target {
public string AString { get; set; }
public int AnInt { get; set; }
public bool ABoolean { get; set; }
public DateTime ADate { get; set; }
public double ADouble { get; set; }
public float AFloat { get; set; }
public decimal ADecimal { get; set; }
public int? ANullableInt { get; set; }
public bool? ANullableBoolean { get; set; }
public DateTime? ANullableDate { get; set; }
public double? ANullableDouble { get; set; }
public float? ANullableFloat { get; set; }
public decimal? ANullableDecimal { get; set; }
}
}
}
@huoxudong125
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment