ColumnHelper, CustomPropertyHelper, FileHelper, TypeHelper code refacturing

This commit is contained in:
wei 2021-07-19 16:34:28 +08:00
parent eb3bf99211
commit 9407cb9c4b
13 changed files with 372 additions and 349 deletions

View File

@ -7,7 +7,8 @@
public class ExcelColumnIndexAttribute : Attribute public class ExcelColumnIndexAttribute : Attribute
{ {
public int ExcelColumnIndex { get; set; } public int ExcelColumnIndex { get; set; }
public ExcelColumnIndexAttribute(string columnName) => Init(Helpers.GetColumnIndex(columnName)); public ExcelColumnIndexAttribute(string columnName) => Init(ColumnHelper
.GetColumnIndex(columnName));
public ExcelColumnIndexAttribute(int columnIndex) => Init(columnIndex); public ExcelColumnIndexAttribute(int columnIndex) => Init(columnIndex);
private void Init(int columnIndex) private void Init(int columnIndex)

View File

@ -5,7 +5,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using static MiniExcelLibs.Utils.Helpers;
namespace MiniExcelLibs.Csv namespace MiniExcelLibs.Csv
{ {
@ -43,7 +42,7 @@ namespace MiniExcelLibs.Csv
continue; continue;
} }
var cell = Helpers.GetEmptyExpandoObject(headRows); var cell = CustomPropertyHelper.GetEmptyExpandoObject(headRows);
for (int i = 0; i <= read.Length - 1; i++) for (int i = 0; i <= read.Length - 1; i++)
cell[headRows[i]] = read[i]; cell[headRows[i]] = read[i];
@ -54,9 +53,9 @@ namespace MiniExcelLibs.Csv
//body //body
{ {
var cell = Helpers.GetEmptyExpandoObject(read.Length - 1, 0); var cell = CustomPropertyHelper.GetEmptyExpandoObject(read.Length - 1, 0);
for (int i = 0; i <= read.Length - 1; i++) for (int i = 0; i <= read.Length - 1; i++)
cell[Helpers.GetAlphabetColumnName(i)] = read[i]; cell[ColumnHelper.GetAlphabetColumnName(i)] = read[i];
yield return cell; yield return cell;
} }
} }
@ -80,7 +79,7 @@ namespace MiniExcelLibs.Csv
row = reader.ReadLine(); row = reader.ReadLine();
read = Split(cf, row); read = Split(cf, row);
var props = Helpers.GetExcelCustomPropertyInfos(type, read); var props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, read);
var index = 0; var index = 0;
foreach (var v in read) foreach (var v in read)
{ {
@ -112,7 +111,7 @@ namespace MiniExcelLibs.Csv
if (itemValue == null) if (itemValue == null)
continue; continue;
newV = TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell); newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell);
} }
} }

View File

@ -7,7 +7,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using static MiniExcelLibs.Utils.Helpers;
namespace MiniExcelLibs.Csv namespace MiniExcelLibs.Csv
{ {
@ -72,7 +71,7 @@ namespace MiniExcelLibs.Csv
{ {
mode = "Properties"; mode = "Properties";
genericType = item.GetType(); genericType = item.GetType();
props = Helpers.GetSaveAsProperties(genericType); props = CustomPropertyHelper.GetSaveAsProperties(genericType);
} }
break; break;

View File

@ -27,7 +27,7 @@
public static IEnumerable<T> Query<T>(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new() public static IEnumerable<T> Query<T>(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new()
{ {
using (var stream = Helpers.OpenSharedRead(path)) using (var stream = FileHelper.OpenSharedRead(path))
foreach (var item in Query<T>(stream, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration)) foreach (var item in Query<T>(stream, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration))
yield return item; //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable yield return item; //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable
} }
@ -39,7 +39,7 @@
public static IEnumerable<dynamic> Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) public static IEnumerable<dynamic> Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{ {
using (var stream = Helpers.OpenSharedRead(path)) using (var stream = FileHelper.OpenSharedRead(path))
foreach (var item in Query(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration)) foreach (var item in Query(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration))
yield return item; yield return item;
} }
@ -76,7 +76,7 @@
/// </summary> /// </summary>
public static DataTable QueryAsDataTable(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) public static DataTable QueryAsDataTable(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{ {
using (var stream = Helpers.OpenSharedRead(path)) using (var stream = FileHelper.OpenSharedRead(path))
return QueryAsDataTable(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration); return QueryAsDataTable(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration);
} }
@ -90,7 +90,7 @@
public static List<string> GetSheetNames(string path) public static List<string> GetSheetNames(string path)
{ {
using (var stream = Helpers.OpenSharedRead(path)) using (var stream = FileHelper.OpenSharedRead(path))
return GetSheetNames(stream); return GetSheetNames(stream);
} }
@ -102,7 +102,7 @@
public static ICollection<string> GetColumns(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) public static ICollection<string> GetColumns(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null)
{ {
using (var stream = Helpers.OpenSharedRead(path)) using (var stream = FileHelper.OpenSharedRead(path))
return GetColumns(stream, useHeaderRow, sheetName, excelType, startCell, configuration); return GetColumns(stream, useHeaderRow, sheetName, excelType, startCell, configuration);
} }

View File

@ -10,7 +10,6 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using static MiniExcelLibs.Utils.Helpers;
namespace MiniExcelLibs.OpenXml namespace MiniExcelLibs.OpenXml
{ {
@ -370,7 +369,7 @@ namespace MiniExcelLibs.OpenXml
private static IDictionary<string, object> GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary<int, string> headRows, int startColumnIndex) private static IDictionary<string, object> GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary<int, string> headRows, int startColumnIndex)
{ {
return useHeaderRow ? Helpers.GetEmptyExpandoObject(headRows) : Helpers.GetEmptyExpandoObject(maxColumnIndex, startColumnIndex); return useHeaderRow ? CustomPropertyHelper.GetEmptyExpandoObject(headRows) : CustomPropertyHelper.GetEmptyExpandoObject(maxColumnIndex, startColumnIndex);
} }
private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Dictionary<int, string> headRows, ref bool isFirstRow, ref IDictionary<string, object> cell, int columnIndex) private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Dictionary<int, string> headRows, ref bool isFirstRow, ref IDictionary<string, object> cell, int columnIndex)
@ -395,7 +394,7 @@ namespace MiniExcelLibs.OpenXml
else else
{ {
//if not using First Head then using A,B,C as index //if not using First Head then using A,B,C as index
cell[Helpers.GetAlphabetColumnName(columnIndex)] = cellValue; cell[ColumnHelper.GetAlphabetColumnName(columnIndex)] = cellValue;
} }
} }
@ -413,7 +412,7 @@ namespace MiniExcelLibs.OpenXml
if (first) if (first)
{ {
//TODO: alert don't duplicate column name //TODO: alert don't duplicate column name
props = Helpers.GetExcelCustomPropertyInfos(type, headers); props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, headers);
first = false; first = false;
} }
var v = new T(); var v = new T();
@ -428,7 +427,7 @@ namespace MiniExcelLibs.OpenXml
if (itemValue == null) if (itemValue == null)
continue; continue;
newV = TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell); newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell);
} }
} }
rowIndex++; rowIndex++;

View File

@ -9,7 +9,6 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using static MiniExcelLibs.Utils.Helpers;
namespace MiniExcelLibs.OpenXml namespace MiniExcelLibs.OpenXml
{ {
@ -139,7 +138,7 @@ namespace MiniExcelLibs.OpenXml
throw new NotImplementedException($"MiniExcel not support only {genericType.Name} value generic type"); throw new NotImplementedException($"MiniExcel not support only {genericType.Name} value generic type");
else if (genericType == typeof(string) || genericType == typeof(DateTime) || genericType == typeof(Guid)) else if (genericType == typeof(string) || genericType == typeof(DateTime) || genericType == typeof(Guid))
throw new NotImplementedException($"MiniExcel not support only {genericType.Name} generic type"); throw new NotImplementedException($"MiniExcel not support only {genericType.Name} generic type");
props = Helpers.GetSaveAsProperties(genericType); props = CustomPropertyHelper.GetSaveAsProperties(genericType);
maxColumnIndex = props.Count; maxColumnIndex = props.Count;
} }
@ -317,7 +316,7 @@ namespace MiniExcelLibs.OpenXml
type = p.ExcludeNullableType; //sometime it doesn't need to re-get type like prop type = p.ExcludeNullableType; //sometime it doesn't need to re-get type like prop
} }
if (Helpers.IsNumericType(type)) if (TypeHelper.IsNumericType(type))
{ {
t = "n"; t = "n";
v = value.ToString(); v = value.ToString();
@ -476,9 +475,9 @@ namespace MiniExcelLibs.OpenXml
else if (maxColumnIndex == 1) else if (maxColumnIndex == 1)
dimensionRef = $"A{maxRowIndex}"; dimensionRef = $"A{maxRowIndex}";
else if (maxRowIndex == 0) else if (maxRowIndex == 0)
dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex - 1)}1"; dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}1";
else else
dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}"; dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}";
return dimensionRef; return dimensionRef;
} }

View File

@ -57,31 +57,31 @@ namespace MiniExcelLibs.OpenXml
//TODO: width,height //TODO: width,height
var xy1 = refs[0]; var xy1 = refs[0];
X1 = Helpers.GetColumnIndex(StringHelper.GetLetter(refs[0])); X1 = ColumnHelper.GetColumnIndex(StringHelper.GetLetter(refs[0]));
Y1 = StringHelper.GetNumber(xy1); Y1 = StringHelper.GetNumber(xy1);
var xy2 = refs[1]; var xy2 = refs[1];
X2 = Helpers.GetColumnIndex(StringHelper.GetLetter(refs[1])); X2 = ColumnHelper.GetColumnIndex(StringHelper.GetLetter(refs[1]));
Y2 = StringHelper.GetNumber(xy2); Y2 = StringHelper.GetNumber(xy2);
Width = Math.Abs(X1 - X2) + 1; ; Width = Math.Abs(X1 - X2) + 1; ;
Height = Math.Abs(Y1 - Y2) + 1; Height = Math.Abs(Y1 - Y2) + 1;
} }
public string XY1 { get { return $"{Helpers.GetAlphabetColumnName(X1)}{Y1}"; } } public string XY1 { get { return $"{ColumnHelper.GetAlphabetColumnName(X1)}{Y1}"; } }
public int X1 { get; set; } public int X1 { get; set; }
public int Y1 { get; set; } public int Y1 { get; set; }
public string XY2 { get { return $"{Helpers.GetAlphabetColumnName(X2)}{Y2}"; } } public string XY2 { get { return $"{ColumnHelper.GetAlphabetColumnName(X2)}{Y2}"; } }
public int X2 { get; set; } public int X2 { get; set; }
public int Y2 { get; set; } public int Y2 { get; set; }
public string Ref { get { return $"{Helpers.GetAlphabetColumnName(X1)}{Y1}:{Helpers.GetAlphabetColumnName(X2)}{Y2}"; } } public string Ref { get { return $"{ColumnHelper.GetAlphabetColumnName(X1)}{Y1}:{ColumnHelper.GetAlphabetColumnName(X2)}{Y2}"; } }
public XmlElement MergeCell { get; set; } public XmlElement MergeCell { get; set; }
public int Width { get; internal set; } public int Width { get; internal set; }
public int Height { get; internal set; } public int Height { get; internal set; }
public string ToXmlString(string prefix) public string ToXmlString(string prefix)
{ {
return $"<{prefix}mergeCell ref=\"{Helpers.GetAlphabetColumnName(X1)}{Y1}:{Helpers.GetAlphabetColumnName(X2)}{Y2}\"/>"; return $"<{prefix}mergeCell ref=\"{ColumnHelper.GetAlphabetColumnName(X1)}{Y1}:{ColumnHelper.GetAlphabetColumnName(X2)}{Y2}\"/>";
} }
} }
@ -520,7 +520,7 @@ namespace MiniExcelLibs.OpenXml
{ {
c.SetAttribute("t", "str"); c.SetAttribute("t", "str");
} }
else if (Helpers.IsNumericType(type)) else if (TypeHelper.IsNumericType(type))
{ {
c.SetAttribute("t", "n"); c.SetAttribute("t", "n");
} }
@ -568,7 +568,7 @@ namespace MiniExcelLibs.OpenXml
{ {
c.SetAttribute("t", "str"); c.SetAttribute("t", "str");
} }
else if (Helpers.IsNumericType(type)) else if (TypeHelper.IsNumericType(type))
{ {
c.SetAttribute("t", "n"); c.SetAttribute("t", "n");
} }

View File

@ -35,7 +35,7 @@ namespace MiniExcelLibs.OpenXml
public void SaveAsByTemplate(string templatePath, object value) public void SaveAsByTemplate(string templatePath, object value)
{ {
using (var stream = Helpers.OpenSharedRead(templatePath)) using (var stream = FileHelper.OpenSharedRead(templatePath))
SaveAsByTemplateImpl(stream, value); SaveAsByTemplateImpl(stream, value);
} }
public void SaveAsByTemplate(byte[] templateBtyes, object value) public void SaveAsByTemplate(byte[] templateBtyes, object value)

View File

@ -0,0 +1,67 @@
namespace MiniExcelLibs.Utils
{
using System.Collections.Generic;
using System.IO;
// For Row/Column Index
internal static partial class ColumnHelper
{
private const int GENERAL_COLUMN_INDEX = 255;
private const int MAX_COLUMN_INDEX = 16383;
private static Dictionary<int, string> _IntMappingAlphabet;
private static Dictionary<string, int> _AlphabetMappingInt;
static ColumnHelper()
{
if (_IntMappingAlphabet == null && _AlphabetMappingInt == null)
{
_IntMappingAlphabet = new Dictionary<int, string>();
_AlphabetMappingInt = new Dictionary<string, int>();
for (int i = 0; i <= GENERAL_COLUMN_INDEX; i++)
{
_IntMappingAlphabet.Add(i, IntToLetters(i));
_AlphabetMappingInt.Add(IntToLetters(i), i);
}
}
}
public static string GetAlphabetColumnName(int columnIndex)
{
CheckAndSetMaxColumnIndex(columnIndex);
return _IntMappingAlphabet[columnIndex];
}
public static int GetColumnIndex(string columnName)
{
var columnIndex = _AlphabetMappingInt[columnName];
CheckAndSetMaxColumnIndex(columnIndex);
return columnIndex;
}
private static void CheckAndSetMaxColumnIndex(int columnIndex)
{
if (columnIndex >= _IntMappingAlphabet.Count)
{
if (columnIndex > MAX_COLUMN_INDEX)
throw new InvalidDataException($"ColumnIndex {columnIndex} over excel vaild max index.");
for (int i = _IntMappingAlphabet.Count; i <= columnIndex; i++)
{
_IntMappingAlphabet.Add(i, IntToLetters(i));
_AlphabetMappingInt.Add(IntToLetters(i), i);
}
}
}
internal static string IntToLetters(int value)
{
value = value + 1;
string result = string.Empty;
while (--value >= 0)
{
result = (char)('A' + value % 26) + result;
value /= 26;
}
return result;
}
}
}

View File

@ -0,0 +1,150 @@
namespace MiniExcelLibs.Utils
{
using MiniExcelLibs.Attributes;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
internal class ExcelCustomPropertyInfo
{
public int? ExcelColumnIndex { get; set; }
public string ExcelColumnName { get; set; }
public PropertyInfo Property { get; set; }
public Type ExcludeNullableType { get; set; }
public bool Nullable { get; internal set; }
public string ExcelFormat { get; internal set; }
}
internal static partial class CustomPropertyHelper
{
internal static IDictionary<string, object> GetEmptyExpandoObject(int maxColumnIndex, int startCellIndex)
{
// TODO: strong type mapping can ignore this
// TODO: it can recode better performance
var cell = (IDictionary<string, object>)new ExpandoObject();
for (int i = startCellIndex; i <= maxColumnIndex; i++)
{
var key = ColumnHelper.GetAlphabetColumnName(i);
if (!cell.ContainsKey(key))
cell.Add(key, null);
}
return cell;
}
internal static IDictionary<string, object> GetEmptyExpandoObject(Dictionary<int, string> hearrows)
{
// TODO: strong type mapping can ignore this
// TODO: it can recode better performance
var cell = (IDictionary<string, object>)new ExpandoObject();
foreach (var hr in hearrows)
if (!cell.ContainsKey(hr.Value))
cell.Add(hr.Value, null);
return cell;
}
internal static List<ExcelCustomPropertyInfo> GetSaveAsProperties(this Type type)
{
List<ExcelCustomPropertyInfo> props = GetExcelPropertyInfo(type, BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.Property.GetGetMethod() != null && !prop.Property.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore))
.ToList() /*ignore without set*/;
if (props.Count == 0)
throw new InvalidOperationException($"{type.Name} un-ignore properties count can't be 0");
// https://github.com/shps951023/MiniExcel/issues/142
//TODO: need optimize performance
{
var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1);
if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1))
throw new InvalidOperationException($"Duplicate column name");
var maxColumnIndex = props.Count - 1;
if (withCustomIndexProps.Any())
maxColumnIndex = Math.Max((int)withCustomIndexProps.Max(w => w.ExcelColumnIndex), maxColumnIndex);
var withoutCustomIndexProps = props.Where(w => w.ExcelColumnIndex == null).ToList();
List<ExcelCustomPropertyInfo> newProps = new List<ExcelCustomPropertyInfo>();
var index = 0;
for (int i = 0; i <= maxColumnIndex; i++)
{
var p1 = withCustomIndexProps.SingleOrDefault(s => s.ExcelColumnIndex == i);
if (p1 != null)
{
newProps.Add(p1);
}
else
{
var p2 = withoutCustomIndexProps.ElementAtOrDefault(index);
if (p2 == null)
{
newProps.Add(null);
}
else
{
p2.ExcelColumnIndex = i;
newProps.Add(p2);
}
index++;
}
}
return newProps;
}
}
internal static List<ExcelCustomPropertyInfo> GetExcelCustomPropertyInfos(Type type, string[] headers)
{
List<ExcelCustomPropertyInfo> props = GetExcelPropertyInfo(type, BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.Property.GetSetMethod() != null && !prop.Property.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore))
.ToList() /*ignore without set*/;
if (props.Count == 0)
throw new InvalidOperationException($"{type.Name} un-ignore properties count can't be 0");
{
var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1);
if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1))
throw new InvalidOperationException($"Duplicate column name");
foreach (var p in props)
{
if (p.ExcelColumnIndex != null)
{
if (p.ExcelColumnIndex >= headers.Length)
throw new ArgumentException($"ExcelColumnIndex {p.ExcelColumnIndex} over haeder max index {headers.Length}");
p.ExcelColumnName = headers[(int)p.ExcelColumnIndex];
if (p.ExcelColumnName == null)
throw new InvalidOperationException($"{p.Property.DeclaringType.Name} {p.Property.Name}'s ExcelColumnIndex {p.ExcelColumnIndex} can't find excel column name");
}
}
}
return props;
}
private static IEnumerable<ExcelCustomPropertyInfo> GetExcelPropertyInfo(Type type, BindingFlags bindingFlags)
{
return type.GetProperties(bindingFlags)
// solve : https://github.com/shps951023/MiniExcel/issues/138
.Select(p =>
{
var gt = Nullable.GetUnderlyingType(p.PropertyType);
var excelNameAttr = p.GetAttribute<ExcelColumnNameAttribute>();
var excelIndexAttr = p.GetAttribute<ExcelColumnIndexAttribute>();
return new ExcelCustomPropertyInfo
{
Property = p,
ExcludeNullableType = gt ?? p.PropertyType,
Nullable = gt != null ? true : false,
ExcelColumnName = excelNameAttr?.ExcelColumnName ?? p.Name,
ExcelColumnIndex = excelIndexAttr?.ExcelColumnIndex,
ExcelFormat = p.GetAttribute<ExcelFormatAttribute>()?.Format,
};
});
}
}
}

View File

@ -0,0 +1,10 @@
namespace MiniExcelLibs.Utils
{
using System.IO;
internal static partial class FileHelper
{
public static FileStream OpenSharedRead(string path) => File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
}
}

View File

@ -1,316 +0,0 @@
namespace MiniExcelLibs.Utils
{
using MiniExcelLibs.Attributes;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
internal static partial class Helpers
{
public static FileStream OpenSharedRead(string path) => File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
}
// For Row/Column Index
internal static partial class Helpers
{
private const int GENERAL_COLUMN_INDEX = 255;
private const int MAX_COLUMN_INDEX = 16383;
private static Dictionary<int, string> _IntMappingAlphabet;
private static Dictionary<string, int> _AlphabetMappingInt;
static Helpers()
{
if (_IntMappingAlphabet == null && _AlphabetMappingInt == null)
{
_IntMappingAlphabet = new Dictionary<int, string>();
_AlphabetMappingInt = new Dictionary<string, int>();
for (int i = 0; i <= GENERAL_COLUMN_INDEX; i++)
{
_IntMappingAlphabet.Add(i, IntToLetters(i));
_AlphabetMappingInt.Add(IntToLetters(i), i);
}
}
}
public static string GetAlphabetColumnName(int columnIndex)
{
CheckAndSetMaxColumnIndex(columnIndex);
return _IntMappingAlphabet[columnIndex];
}
public static int GetColumnIndex(string columnName)
{
var columnIndex = _AlphabetMappingInt[columnName];
CheckAndSetMaxColumnIndex(columnIndex);
return columnIndex;
}
private static void CheckAndSetMaxColumnIndex(int columnIndex)
{
if (columnIndex >= _IntMappingAlphabet.Count)
{
if (columnIndex > MAX_COLUMN_INDEX)
throw new InvalidDataException($"ColumnIndex {columnIndex} over excel vaild max index.");
for (int i = _IntMappingAlphabet.Count; i <= columnIndex; i++)
{
_IntMappingAlphabet.Add(i, IntToLetters(i));
_AlphabetMappingInt.Add(IntToLetters(i), i);
}
}
}
internal static string IntToLetters(int value)
{
value = value + 1;
string result = string.Empty;
while (--value >= 0)
{
result = (char)('A' + value % 26) + result;
value /= 26;
}
return result;
}
}
internal static partial class Helpers
{
internal static IDictionary<string, object> GetEmptyExpandoObject(int maxColumnIndex, int startCellIndex)
{
// TODO: strong type mapping can ignore this
// TODO: it can recode better performance
var cell = (IDictionary<string, object>)new ExpandoObject();
for (int i = startCellIndex; i <= maxColumnIndex; i++)
{
var key = GetAlphabetColumnName(i);
if (!cell.ContainsKey(key))
cell.Add(key, null);
}
return cell;
}
internal static IDictionary<string, object> GetEmptyExpandoObject(Dictionary<int, string> hearrows)
{
// TODO: strong type mapping can ignore this
// TODO: it can recode better performance
var cell = (IDictionary<string, object>)new ExpandoObject();
foreach (var hr in hearrows)
if (!cell.ContainsKey(hr.Value))
cell.Add(hr.Value, null);
return cell;
}
internal static List<ExcelCustomPropertyInfo> GetSaveAsProperties(this Type type)
{
List<ExcelCustomPropertyInfo> props = GetExcelPropertyInfo(type, BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.Property.GetGetMethod() != null && !prop.Property.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore))
.ToList() /*ignore without set*/;
if (props.Count == 0)
throw new InvalidOperationException($"{type.Name} un-ignore properties count can't be 0");
// https://github.com/shps951023/MiniExcel/issues/142
//TODO: need optimize performance
{
var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1);
if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1))
throw new InvalidOperationException($"Duplicate column name");
var maxColumnIndex = props.Count - 1;
if (withCustomIndexProps.Any())
maxColumnIndex = Math.Max((int)withCustomIndexProps.Max(w => w.ExcelColumnIndex), maxColumnIndex);
var withoutCustomIndexProps = props.Where(w => w.ExcelColumnIndex == null).ToList();
List<ExcelCustomPropertyInfo> newProps = new List<ExcelCustomPropertyInfo>();
var index = 0;
for (int i = 0; i <= maxColumnIndex; i++)
{
var p1 = withCustomIndexProps.SingleOrDefault(s => s.ExcelColumnIndex == i);
if (p1 != null)
{
newProps.Add(p1);
}
else
{
var p2 = withoutCustomIndexProps.ElementAtOrDefault(index);
if (p2 == null)
{
newProps.Add(null);
}
else
{
p2.ExcelColumnIndex = i;
newProps.Add(p2);
}
index++;
}
}
return newProps;
}
}
internal class ExcelCustomPropertyInfo
{
public int? ExcelColumnIndex { get; set; }
public string ExcelColumnName { get; set; }
public PropertyInfo Property { get; set; }
public Type ExcludeNullableType { get; set; }
public bool Nullable { get; internal set; }
public string ExcelFormat { get; internal set; }
}
internal static List<ExcelCustomPropertyInfo> GetExcelCustomPropertyInfos(Type type, string[] headers)
{
List<ExcelCustomPropertyInfo> props = GetExcelPropertyInfo(type, BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.Property.GetSetMethod() != null && !prop.Property.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore))
.ToList() /*ignore without set*/;
if (props.Count == 0)
throw new InvalidOperationException($"{type.Name} un-ignore properties count can't be 0");
{
var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1);
if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1))
throw new InvalidOperationException($"Duplicate column name");
foreach (var p in props)
{
if (p.ExcelColumnIndex != null)
{
if (p.ExcelColumnIndex >= headers.Length)
throw new ArgumentException($"ExcelColumnIndex {p.ExcelColumnIndex} over haeder max index {headers.Length}");
p.ExcelColumnName = headers[(int)p.ExcelColumnIndex];
if (p.ExcelColumnName == null)
throw new InvalidOperationException($"{p.Property.DeclaringType.Name} {p.Property.Name}'s ExcelColumnIndex {p.ExcelColumnIndex} can't find excel column name");
}
}
}
return props;
}
private static IEnumerable<ExcelCustomPropertyInfo> GetExcelPropertyInfo(Type type, BindingFlags bindingFlags)
{
return type.GetProperties(bindingFlags)
// solve : https://github.com/shps951023/MiniExcel/issues/138
.Select(p =>
{
var gt = Nullable.GetUnderlyingType(p.PropertyType);
var excelNameAttr = p.GetAttribute<ExcelColumnNameAttribute>();
var excelIndexAttr = p.GetAttribute<ExcelColumnIndexAttribute>();
return new ExcelCustomPropertyInfo
{
Property = p,
ExcludeNullableType = gt ?? p.PropertyType,
Nullable = gt != null ? true : false,
ExcelColumnName = excelNameAttr?.ExcelColumnName ?? p.Name,
ExcelColumnIndex = excelIndexAttr?.ExcelColumnIndex,
ExcelFormat = p.GetAttribute<ExcelFormatAttribute>()?.Format,
};
});
}
public static bool IsNumericType(Type type, bool isNullableUnderlyingType = false)
{
if (isNullableUnderlyingType)
type = Nullable.GetUnderlyingType(type) ?? type;
switch (Type.GetTypeCode(type))
{
//case TypeCode.Byte:
//case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return true;
default:
return false;
}
}
public static object TypeMapping<T>(T v, ExcelCustomPropertyInfo pInfo, object newValue, object itemValue, int rowIndex, string startCell) where T : class, new()
{
try
{
return TypeMappingImpl(v, pInfo, ref newValue, itemValue);
}
catch (Exception ex) when (ex is InvalidCastException || ex is FormatException)
{
var columnName = pInfo.ExcelColumnName ?? pInfo.Property.Name;
var startRowIndex = ReferenceHelper.ConvertCellToXY(startCell).Item2;
var errorRow = startRowIndex + rowIndex + 1;
throw new InvalidCastException($"ColumnName : {columnName}, CellRow : {errorRow}, Value : {itemValue}, it can't cast to {pInfo.Property.PropertyType.Name} type.");
}
}
private static object TypeMappingImpl<T>(T v, ExcelCustomPropertyInfo pInfo, ref object newValue, object itemValue) where T : class, new()
{
if (pInfo.ExcludeNullableType == typeof(Guid))
{
newValue = Guid.Parse(itemValue.ToString());
}
else if (pInfo.ExcludeNullableType == typeof(DateTime))
{
// fix issue 257 https://github.com/shps951023/MiniExcel/issues/257
if (itemValue is DateTime || itemValue is DateTime?)
{
newValue = itemValue;
pInfo.Property.SetValue(v, newValue);
return newValue;
}
var vs = itemValue?.ToString();
if (pInfo.ExcelFormat != null)
{
if (DateTime.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var _v))
{
newValue = _v;
}
}
else if (DateTime.TryParse(vs, CultureInfo.InvariantCulture, DateTimeStyles.None, out var _v))
newValue = _v;
else if (DateTime.TryParseExact(vs, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var _v2))
newValue = _v2;
else if (double.TryParse(vs, NumberStyles.None, CultureInfo.InvariantCulture, out var _d))
newValue = DateTimeHelper.FromOADate(_d);
else
throw new InvalidCastException($"{vs} can't cast to datetime");
}
else if (pInfo.ExcludeNullableType == typeof(bool))
{
var vs = itemValue.ToString();
if (vs == "1")
newValue = true;
else if (vs == "0")
newValue = false;
else
newValue = bool.Parse(vs);
}
else if (pInfo.Property.PropertyType == typeof(string))
{
newValue = XmlEncoder.DecodeString(itemValue?.ToString());
}
else if (pInfo.Property.PropertyType.IsEnum)
{
newValue = Enum.Parse(pInfo.Property.PropertyType, itemValue?.ToString(), true);
}
else
{
// Use pInfo.ExcludeNullableType to resolve : https://github.com/shps951023/MiniExcel/issues/138
newValue = Convert.ChangeType(itemValue, pInfo.ExcludeNullableType);
}
pInfo.Property.SetValue(v, newValue);
return newValue;
}
}
}

View File

@ -0,0 +1,115 @@
namespace MiniExcelLibs.Utils
{
using MiniExcelLibs.Attributes;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
internal static partial class TypeHelper
{
public static bool IsNumericType(Type type, bool isNullableUnderlyingType = false)
{
if (isNullableUnderlyingType)
type = Nullable.GetUnderlyingType(type) ?? type;
switch (Type.GetTypeCode(type))
{
//case TypeCode.Byte:
//case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return true;
default:
return false;
}
}
public static object TypeMapping<T>(T v, ExcelCustomPropertyInfo pInfo, object newValue, object itemValue, int rowIndex, string startCell) where T : class, new()
{
try
{
return TypeMappingImpl(v, pInfo, ref newValue, itemValue);
}
catch (Exception ex) when (ex is InvalidCastException || ex is FormatException)
{
var columnName = pInfo.ExcelColumnName ?? pInfo.Property.Name;
var startRowIndex = ReferenceHelper.ConvertCellToXY(startCell).Item2;
var errorRow = startRowIndex + rowIndex + 1;
throw new InvalidCastException($"ColumnName : {columnName}, CellRow : {errorRow}, Value : {itemValue}, it can't cast to {pInfo.Property.PropertyType.Name} type.");
}
}
private static object TypeMappingImpl<T>(T v, ExcelCustomPropertyInfo pInfo, ref object newValue, object itemValue) where T : class, new()
{
if (pInfo.ExcludeNullableType == typeof(Guid))
{
newValue = Guid.Parse(itemValue.ToString());
}
else if (pInfo.ExcludeNullableType == typeof(DateTime))
{
// fix issue 257 https://github.com/shps951023/MiniExcel/issues/257
if (itemValue is DateTime || itemValue is DateTime?)
{
newValue = itemValue;
pInfo.Property.SetValue(v, newValue);
return newValue;
}
var vs = itemValue?.ToString();
if (pInfo.ExcelFormat != null)
{
if (DateTime.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var _v))
{
newValue = _v;
}
}
else if (DateTime.TryParse(vs, CultureInfo.InvariantCulture, DateTimeStyles.None, out var _v))
newValue = _v;
else if (DateTime.TryParseExact(vs, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var _v2))
newValue = _v2;
else if (double.TryParse(vs, NumberStyles.None, CultureInfo.InvariantCulture, out var _d))
newValue = DateTimeHelper.FromOADate(_d);
else
throw new InvalidCastException($"{vs} can't cast to datetime");
}
else if (pInfo.ExcludeNullableType == typeof(bool))
{
var vs = itemValue.ToString();
if (vs == "1")
newValue = true;
else if (vs == "0")
newValue = false;
else
newValue = bool.Parse(vs);
}
else if (pInfo.Property.PropertyType == typeof(string))
{
newValue = XmlEncoder.DecodeString(itemValue?.ToString());
}
else if (pInfo.Property.PropertyType.IsEnum)
{
newValue = Enum.Parse(pInfo.Property.PropertyType, itemValue?.ToString(), true);
}
else
{
// Use pInfo.ExcludeNullableType to resolve : https://github.com/shps951023/MiniExcel/issues/138
newValue = Convert.ChangeType(itemValue, pInfo.ExcludeNullableType);
}
pInfo.Property.SetValue(v, newValue);
return newValue;
}
}
}