Skip to content

Instantly share code, notes, and snippets.

@yodiz
Created October 11, 2013 09:26
Show Gist options
  • Save yodiz/6932018 to your computer and use it in GitHub Desktop.
Save yodiz/6932018 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
namespace GLIM.Utilities.Text
{
public enum FieldOption
{
Required = 0,
Optional = 1
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class FixedRecordAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class FixedAttribute : Attribute
{
public int Length { get; private set; }
public FieldOption Option { get; private set; }
public FixedAttribute(int length, FieldOption option = FieldOption.Required)
{
Length = length;
Option = option;
}
}
/// <summary>
/// Parses a line of text into a data-structure decorated with the FixedAttribute.
/// The line consist of fields of a fixed length that corisponds to the given data-structure
/// </summary>
public static class Fixed<T>
{
public static T Parse(string line)
{
return (T)Fixed.Parse(typeof(T), line);
}
public static int Length()
{
return Fixed.Length(typeof(T));
}
}
public static class Fixed
{
private class FieldInfo
{
public readonly FixedAttribute Attr;
public readonly Action<object, object> SetValue;
public readonly Func<string, object> Convert;
public FieldInfo(FixedAttribute attr, Action<object, object> setValue, Func<string, object> converter)
{
Attr = attr;
SetValue = setValue;
Convert = converter;
}
}
public static object Parse(Type ofType, string line)
{
return CreateInstance(ofType, line, GetFieldInfo(ofType));
}
private static object CreateInstance(Type ofType, string line, IEnumerable<FieldInfo> fields)
{
var obj = Activator.CreateInstance(ofType);
var current = 0;
foreach (var mf in fields)
{
var length = mf.Attr.Length;
if (mf.Attr.Option == FieldOption.Optional && (current + length > line.Length))
{
//Field is marked as optional and the length given is larger than the length of the line
}
else
{
var value = line.Substring(current, length);
var convertedValue = mf.Convert(value);
mf.SetValue(obj, convertedValue);
}
current += length;
}
return obj;
}
private static readonly Dictionary<Type, FieldInfo[]> CachedReflection = new Dictionary<Type, FieldInfo[]>();
private static IEnumerable<FieldInfo> GetFieldInfo(Type forType)
{
if (!CachedReflection.ContainsKey(forType))
CachedReflection[forType] = ReflectFieldsAndProperties(forType);
return CachedReflection[forType];
}
private static Func<string, object> ConverterFor(Type type)
{
Func<string, object> enumConv =
// Its questionable to set the default value of the enum if the value is empty.
// A correct behavior would be to throw an error
// or have a type representing that the value can be missing
// ie. Nullable<EnumType>.
value => (value.Trim().Length == 0) ? Activator.CreateInstance(type) : Enum.ToObject(type, Convert.ChangeType(value, Enum.GetUnderlyingType(type)));
return type.BaseType == typeof(Enum) ? enumConv : value => Convert.ChangeType(value.Trim(), type);
}
private static FieldInfo[] ReflectFieldsAndProperties(Type forType)
{
var membersWithFixed =
forType.GetFields()
.Select(m => {
var attrs = m.GetCustomAttributes(typeof(FixedAttribute), false).OfType<FixedAttribute>().ToArray();
var fattrs = m.GetCustomAttributes(typeof(FixedRecordAttribute), false).OfType<FixedRecordAttribute>();
if (attrs.Any())
{
var attr = attrs.First();
return new FieldInfo(attr, m.SetValue, ConverterFor(m.FieldType));
}
if (fattrs.Any())
{
var length = Length(m.FieldType);
return new FieldInfo(new FixedAttribute(length), m.SetValue, s => Parse(m.FieldType, s));
}
return null;
}).Where(x => x != null);
var propertiesWithFixed =
forType.GetProperties()
.Select(m => {
var attrs = m.GetCustomAttributes(typeof(FixedAttribute), false).OfType<FixedAttribute>().ToArray();
var fattrs = m.GetCustomAttributes(typeof(FixedRecordAttribute), false).OfType<FixedRecordAttribute>();
if (attrs.Any())
{
var attr = attrs.First();
return new FieldInfo(attr, m.SetValue, ConverterFor(m.PropertyType));
}
if (fattrs.Any())
{
var length = Length(m.PropertyType);
return new FieldInfo(
new FixedAttribute(length),
m.SetValue,
s => Parse(m.PropertyType, s));
}
return null;
}).Where(x => x != null);
var fields = membersWithFixed.Concat(propertiesWithFixed).ToArray();
return fields;
}
public static int Length(Type ofType)
{
return GetFieldInfo(ofType).Select(x => x.Attr.Length).Sum();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment