Car/Assets/Plugin/YogiGameCore/FlexReader/Mapping/MapperBase.cs

292 lines
10 KiB
C#
Raw Normal View History

2024-12-31 07:57:41 +08:00
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<T> where T : MapperBase<T>
{
protected readonly Mapping[] mappings;
protected readonly Type type;
/// <summary>
/// No exception will be thrown when parsing columns
/// </summary>
/// <returns></returns>
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();
}
/// <summary>
/// Get serializable members
/// </summary>
/// <returns>Members</returns>
protected virtual MemberInfo[] GetMembers()
{
return FormatterServices.GetSerializableMembers(type);
}
/// <summary>
/// Create an object instance
/// </summary>
/// <returns></returns>
protected virtual object CreateInstance()
{
return FormatterServices.GetUninitializedObject(type);
}
/// <summary>
/// Assign data to members
/// </summary>
/// <param name="obj">Object instance</param>
/// <param name="members">Members</param>
/// <param name="data">Data</param>
protected virtual void Assign(object obj, MemberInfo[] members, object[] data)
{
FormatterServices.PopulateObjectMembers(obj, members, data);
}
/// <summary>
/// Extract mapping info from class attributes
/// </summary>
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<object> 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;
}
}
/// <summary>
/// Map member to column
/// </summary>
/// <param name="member">Member name</param>
/// <param name="column">One-based column index</param>
/// <returns>Mapping instance</returns>
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;
}
/// <summary>
/// Map member to column
/// </summary>
/// <param name="member">Member name</param>
/// <param name="column">One-based column index</param>
/// <param name="default">Fallback value</param>
/// <returns>Mapping instance</returns>
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;
}
/// <summary>
/// Map member to column
/// </summary>
/// <param name="member">Member name</param>
/// <param name="column">Column name</param>
/// <returns>Mapping instance</returns>
public T Map(string member, string column)
{
return Map(member, Address.ParseColumn(column));
}
/// <summary>
/// Map member to column
/// </summary>
/// <param name="member">Member name</param>
/// <param name="column">Column name</param>
/// <param name="default">Fallback value</param>
/// <returns>Mapping instance</returns>
public T Map(string member, string column, object @default)
{
return Map(member, Address.ParseColumn(column), @default);
}
/// <summary>
/// Map members from expressions(e.g. name:1, age:2, role:3)
/// </summary>
/// <remarks>
/// Expressions format: member:column, member:column...
/// </remarks>
/// <param name="expression">Expression</param>
/// <returns>Mapping instance</returns>
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;
}
/// <summary>
/// Map members from columns
/// </summary>
/// <remarks>
/// Each column provides mapping member name and column index
/// </remarks>
/// <param name="row">Row</param>
/// <returns>Mapping instance</returns>
public T Map(IEnumerable<Cell> row)
{
foreach (var column in row)
{
Map(column.Text, column.Address.Column);
}
return (T)this;
}
/// <summary>
/// Remove member mapping
/// </summary>
/// <param name="member">Member name</param>
/// <returns>Mapping instance</returns>
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;
}
/// <summary>
/// Clear member mapping
/// </summary>
/// <returns>Mapping instance</returns>
public T Clear()
{
Array.ForEach(mappings, m => m.Column = 0);
return (T)this;
}
/// <summary>
/// Copy mapping from target mapping instance
/// </summary>
/// <param name="mapping">Mapping instance to copy from</param>
/// <returns>Mapping instance</returns>
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;
}
/// <summary>
/// Create an object instance from target row
/// </summary>
/// <param name="row">Row</param>
/// <returns>Object instance</returns>
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;
}
}
}