mirror of
https://gitee.com/dotnetchina/MiniExcel.git
synced 2024-11-29 18:38:08 +08:00
ColumnHelper, CustomPropertyHelper, FileHelper, TypeHelper code refacturing
This commit is contained in:
parent
eb3bf99211
commit
9407cb9c4b
@ -7,7 +7,8 @@
|
||||
public class ExcelColumnIndexAttribute : Attribute
|
||||
{
|
||||
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);
|
||||
|
||||
private void Init(int columnIndex)
|
||||
|
@ -5,7 +5,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using static MiniExcelLibs.Utils.Helpers;
|
||||
|
||||
namespace MiniExcelLibs.Csv
|
||||
{
|
||||
@ -43,7 +42,7 @@ namespace MiniExcelLibs.Csv
|
||||
continue;
|
||||
}
|
||||
|
||||
var cell = Helpers.GetEmptyExpandoObject(headRows);
|
||||
var cell = CustomPropertyHelper.GetEmptyExpandoObject(headRows);
|
||||
for (int i = 0; i <= read.Length - 1; i++)
|
||||
cell[headRows[i]] = read[i];
|
||||
|
||||
@ -54,9 +53,9 @@ namespace MiniExcelLibs.Csv
|
||||
|
||||
//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++)
|
||||
cell[Helpers.GetAlphabetColumnName(i)] = read[i];
|
||||
cell[ColumnHelper.GetAlphabetColumnName(i)] = read[i];
|
||||
yield return cell;
|
||||
}
|
||||
}
|
||||
@ -80,7 +79,7 @@ namespace MiniExcelLibs.Csv
|
||||
row = reader.ReadLine();
|
||||
read = Split(cf, row);
|
||||
|
||||
var props = Helpers.GetExcelCustomPropertyInfos(type, read);
|
||||
var props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, read);
|
||||
var index = 0;
|
||||
foreach (var v in read)
|
||||
{
|
||||
@ -112,7 +111,7 @@ namespace MiniExcelLibs.Csv
|
||||
if (itemValue == null)
|
||||
continue;
|
||||
|
||||
newV = TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell);
|
||||
newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static MiniExcelLibs.Utils.Helpers;
|
||||
|
||||
namespace MiniExcelLibs.Csv
|
||||
{
|
||||
@ -72,7 +71,7 @@ namespace MiniExcelLibs.Csv
|
||||
{
|
||||
mode = "Properties";
|
||||
genericType = item.GetType();
|
||||
props = Helpers.GetSaveAsProperties(genericType);
|
||||
props = CustomPropertyHelper.GetSaveAsProperties(genericType);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -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()
|
||||
{
|
||||
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))
|
||||
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)
|
||||
{
|
||||
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))
|
||||
yield return item;
|
||||
}
|
||||
@ -76,7 +76,7 @@
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
|
||||
public static List<string> GetSheetNames(string path)
|
||||
{
|
||||
using (var stream = Helpers.OpenSharedRead(path))
|
||||
using (var stream = FileHelper.OpenSharedRead(path))
|
||||
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)
|
||||
{
|
||||
using (var stream = Helpers.OpenSharedRead(path))
|
||||
using (var stream = FileHelper.OpenSharedRead(path))
|
||||
return GetColumns(stream, useHeaderRow, sheetName, excelType, startCell, configuration);
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,6 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using static MiniExcelLibs.Utils.Helpers;
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
@ -395,7 +394,7 @@ namespace MiniExcelLibs.OpenXml
|
||||
else
|
||||
{
|
||||
//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)
|
||||
{
|
||||
//TODO: alert don't duplicate column name
|
||||
props = Helpers.GetExcelCustomPropertyInfos(type, headers);
|
||||
props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, headers);
|
||||
first = false;
|
||||
}
|
||||
var v = new T();
|
||||
@ -428,7 +427,7 @@ namespace MiniExcelLibs.OpenXml
|
||||
if (itemValue == null)
|
||||
continue;
|
||||
|
||||
newV = TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell);
|
||||
newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell);
|
||||
}
|
||||
}
|
||||
rowIndex++;
|
||||
|
@ -9,7 +9,6 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static MiniExcelLibs.Utils.Helpers;
|
||||
|
||||
namespace MiniExcelLibs.OpenXml
|
||||
{
|
||||
@ -139,7 +138,7 @@ namespace MiniExcelLibs.OpenXml
|
||||
throw new NotImplementedException($"MiniExcel not support only {genericType.Name} value generic type");
|
||||
else if (genericType == typeof(string) || genericType == typeof(DateTime) || genericType == typeof(Guid))
|
||||
throw new NotImplementedException($"MiniExcel not support only {genericType.Name} generic type");
|
||||
props = Helpers.GetSaveAsProperties(genericType);
|
||||
props = CustomPropertyHelper.GetSaveAsProperties(genericType);
|
||||
maxColumnIndex = props.Count;
|
||||
}
|
||||
|
||||
@ -317,7 +316,7 @@ namespace MiniExcelLibs.OpenXml
|
||||
type = p.ExcludeNullableType; //sometime it doesn't need to re-get type like prop
|
||||
}
|
||||
|
||||
if (Helpers.IsNumericType(type))
|
||||
if (TypeHelper.IsNumericType(type))
|
||||
{
|
||||
t = "n";
|
||||
v = value.ToString();
|
||||
@ -476,9 +475,9 @@ namespace MiniExcelLibs.OpenXml
|
||||
else if (maxColumnIndex == 1)
|
||||
dimensionRef = $"A{maxRowIndex}";
|
||||
else if (maxRowIndex == 0)
|
||||
dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex - 1)}1";
|
||||
dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}1";
|
||||
else
|
||||
dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}";
|
||||
dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}";
|
||||
return dimensionRef;
|
||||
}
|
||||
|
||||
|
@ -57,31 +57,31 @@ namespace MiniExcelLibs.OpenXml
|
||||
|
||||
//TODO: width,height
|
||||
var xy1 = refs[0];
|
||||
X1 = Helpers.GetColumnIndex(StringHelper.GetLetter(refs[0]));
|
||||
X1 = ColumnHelper.GetColumnIndex(StringHelper.GetLetter(refs[0]));
|
||||
Y1 = StringHelper.GetNumber(xy1);
|
||||
|
||||
var xy2 = refs[1];
|
||||
X2 = Helpers.GetColumnIndex(StringHelper.GetLetter(refs[1]));
|
||||
X2 = ColumnHelper.GetColumnIndex(StringHelper.GetLetter(refs[1]));
|
||||
Y2 = StringHelper.GetNumber(xy2);
|
||||
|
||||
Width = Math.Abs(X1 - X2) + 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 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 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 int Width { get; internal set; }
|
||||
public int Height { get; internal set; }
|
||||
|
||||
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");
|
||||
}
|
||||
else if (Helpers.IsNumericType(type))
|
||||
else if (TypeHelper.IsNumericType(type))
|
||||
{
|
||||
c.SetAttribute("t", "n");
|
||||
}
|
||||
@ -568,7 +568,7 @@ namespace MiniExcelLibs.OpenXml
|
||||
{
|
||||
c.SetAttribute("t", "str");
|
||||
}
|
||||
else if (Helpers.IsNumericType(type))
|
||||
else if (TypeHelper.IsNumericType(type))
|
||||
{
|
||||
c.SetAttribute("t", "n");
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ namespace MiniExcelLibs.OpenXml
|
||||
|
||||
public void SaveAsByTemplate(string templatePath, object value)
|
||||
{
|
||||
using (var stream = Helpers.OpenSharedRead(templatePath))
|
||||
using (var stream = FileHelper.OpenSharedRead(templatePath))
|
||||
SaveAsByTemplateImpl(stream, value);
|
||||
}
|
||||
public void SaveAsByTemplate(byte[] templateBtyes, object value)
|
||||
|
67
src/MiniExcel/Utils/ColumnHelper.cs
Normal file
67
src/MiniExcel/Utils/ColumnHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
150
src/MiniExcel/Utils/CustomPropertyHelper.cs
Normal file
150
src/MiniExcel/Utils/CustomPropertyHelper.cs
Normal 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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
10
src/MiniExcel/Utils/FileHelper.cs
Normal file
10
src/MiniExcel/Utils/FileHelper.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
115
src/MiniExcel/Utils/TypeHelper.cs
Normal file
115
src/MiniExcel/Utils/TypeHelper.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user