using System; using System.Linq; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; using System.Runtime.Serialization; namespace FlexFramework.Excel { public abstract class MapperBase where T : MapperBase { protected readonly Mapping[] mappings; protected readonly Type type; /// /// No exception will be thrown when parsing columns /// /// public bool SafeMode { get; set; } protected MapperBase(Type type) { if (type == null) throw new ArgumentNullException("type"); this.type = type; var members = GetMembers(); mappings = members.Select(m => new Mapping(m)).ToArray(); } /// /// Get serializable members /// /// Members protected virtual MemberInfo[] GetMembers() { return FormatterServices.GetSerializableMembers(type); } /// /// Create an object instance /// /// protected virtual object CreateInstance() { return FormatterServices.GetUninitializedObject(type); } /// /// Assign data to members /// /// Object instance /// Members /// Data protected virtual void Assign(object obj, MemberInfo[] members, object[] data) { FormatterServices.PopulateObjectMembers(obj, members, data); } /// /// Extract mapping info from class attributes /// public virtual void Extract() { foreach (var mapping in mappings) { var attribute = Attribute.GetCustomAttribute(mapping.Member, typeof(ColumnAttribute)) as ColumnAttribute; if (attribute == null) mapping.Column = 0; else { if (attribute.Column < 1) { throw new ArgumentException("One-based column index must be greater than 0"); } mapping.Column = attribute.Column; mapping.Default = attribute.Default; mapping.Fallback = attribute.Fallback; } } } protected IEnumerable Cast(Row row) { foreach (var mapping in mappings.Where(m => m.Column > 0)) { var index = mapping.Column - 1; if (index >= row.Count) { throw new InvalidOperationException(string.Format("Column '{0}' index '{1}' out of range '{2}' on type '{3}'", mapping.Member.Name, index, row.Count, type.FullName)); } Object value = null; try { value = row[index].Convert(mapping.Type); } catch (FormatException) { if (mapping.Fallback) value = mapping.Default; else if (SafeMode) mapping.Failed = true; else throw new FormatException(string.Format("Input cell at {0} was not in the correct format for type {1}", row[index].Address, mapping.Type)); } if (!mapping.Failed) yield return value; } } /// /// Map member to column /// /// Member name /// One-based column index /// Mapping instance public T Map(string member, int column) { var mapping = Array.Find(mappings, m => m.Member.Name == member); if (mapping == null) { throw new InvalidOperationException(string.Format("Member '{0}' not found in type '{1}'", member, type.FullName)); } if (mapping.Column > 0) { throw new InvalidOperationException(string.Format("Member '{0}' for type '{1}' already mapped to Column '{2}'", member, type.FullName, mapping.Column)); } if (column < 1) { throw new ArgumentException("One-based column index must be greater than 0"); } mapping.Column = column; return (T)this; } /// /// Map member to column /// /// Member name /// One-based column index /// Fallback value /// Mapping instance public T Map(string member, int column, object @default) { var mapping = Array.Find(mappings, m => m.Member.Name == member); if (mapping == null) { throw new InvalidOperationException(string.Format("Member '{0}' not found in type '{1}'", member, type.FullName)); } if (mapping.Column > 0) { throw new InvalidOperationException(string.Format("Member '{0}' for type '{1}' already mapped to Column '{2}'", member, type.FullName, mapping.Column)); } if (column < 1) { throw new ArgumentException("One-based column index must be greater than 0"); } mapping.Default = @default; mapping.Column = column; mapping.Fallback = true; return (T)this; } /// /// Map member to column /// /// Member name /// Column name /// Mapping instance public T Map(string member, string column) { return Map(member, Address.ParseColumn(column)); } /// /// Map member to column /// /// Member name /// Column name /// Fallback value /// Mapping instance public T Map(string member, string column, object @default) { return Map(member, Address.ParseColumn(column), @default); } /// /// Map members from expressions(e.g. name:1, age:2, role:3) /// /// /// Expressions format: member:column, member:column... /// /// Expression /// Mapping instance public T Map(string expression) { if (!Regex.IsMatch(expression, "^(\\w+\\s*:(\\d+|[A-Z]+)\\s*)(,\\s*\\w+\\s*:(\\d+|[A-Z]+)\\s*)*$")) { throw new FormatException("Invalid mapping expression"); } var groups = expression.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); foreach (var group in groups) { var entires = group.Split(new[] { ":" }, StringSplitOptions.RemoveEmptyEntries); var column = entires[1].Trim(); if (Regex.IsMatch(column, "^\\d+$")) Map(entires[0].Trim(), Int32.Parse(column)); else Map(entires[0].Trim(), column); } return (T)this; } /// /// Map members from columns /// /// /// Each column provides mapping member name and column index /// /// Row /// Mapping instance public T Map(IEnumerable row) { foreach (var column in row) { Map(column.Text, column.Address.Column); } return (T)this; } /// /// Remove member mapping /// /// Member name /// Mapping instance public T Remove(string member) { var mapping = Array.Find(mappings, m => m.Member.Name == member); if (mapping == null) { throw new InvalidOperationException(string.Format("Member '{0}' not found in type '{1}'", member, type.FullName)); } mapping.Column = 0; return (T)this; } /// /// Clear member mapping /// /// Mapping instance public T Clear() { Array.ForEach(mappings, m => m.Column = 0); return (T)this; } /// /// Copy mapping from target mapping instance /// /// Mapping instance to copy from /// Mapping instance public T Copy(T mapping) { Array.ForEach(mappings, m => m.Column = 0); foreach (var source in mapping.mappings) { var target = Array.Find(mappings, m => m.Member.Name == source.Member.Name && m.Member.DeclaringType == source.Member.DeclaringType); if (target != null) { target.Column = source.Column; target.Default = source.Default; target.Fallback = source.Fallback; } } return (T)this; } /// /// Create an object instance from target row /// /// Row /// Object instance protected object Instantiate(Row row) { var obj = CreateInstance(); var data = Cast(row).ToArray(); var members = mappings.Where(m => m.Column > 0 && !m.Failed).Select(m => m.Member).ToArray(); Array.ForEach(mappings, m => m.Failed = false); Assign(obj, members, data); return obj; } } }