Skip to content

Commit

Permalink
#1052, #1058 LoadFromCollection filter nested class propertiesbased o…
Browse files Browse the repository at this point in the history
…n the supplied list of MemberInfo
  • Loading branch information
swmal committed Sep 14, 2023
1 parent 141264d commit 525e0d2
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 41 deletions.
12 changes: 6 additions & 6 deletions src/EPPlus/ExcelRangeBase_Load.cs
Original file line number Diff line number Diff line change
Expand Up @@ -318,18 +318,18 @@ public ExcelRangeBase LoadFromCollection<T>(IEnumerable<T> Collection, bool Prin
/// <param name="memberFlags">Property flags to use</param>
/// <param name="Members">The properties to output. Must be of type T</param>
/// <returns>The filled range</returns>
public ExcelRangeBase LoadFromCollection<T>(IEnumerable<T> Collection, bool PrintHeaders, TableStyles? TableStyle, BindingFlags memberFlags, MemberInfo[] Members)
public ExcelRangeBase LoadFromCollection<T>(IEnumerable<T> Collection, bool? PrintHeaders, TableStyles? TableStyle, BindingFlags memberFlags, MemberInfo[] Members)
{
return LoadFromCollectionInternal(Collection, PrintHeaders, TableStyle, memberFlags, Members);
}

private ExcelRangeBase LoadFromCollectionInternal<T>(IEnumerable<T> Collection, bool PrintHeaders, TableStyles? TableStyle, BindingFlags memberFlags, MemberInfo[] Members)
private ExcelRangeBase LoadFromCollectionInternal<T>(IEnumerable<T> Collection, bool? PrintHeaders, TableStyles? TableStyle, BindingFlags memberFlags, MemberInfo[] Members)
{
if (Collection is IEnumerable<IDictionary<string, object>>)
{
if (Members == null)
return LoadFromDictionaries(Collection as IEnumerable<IDictionary<string, object>>, PrintHeaders, TableStyle);
return LoadFromDictionaries(Collection as IEnumerable<IDictionary<string, object>>, PrintHeaders, TableStyle, Members.Select(x => x.Name));
return LoadFromDictionaries(Collection as IEnumerable<IDictionary<string, object>>, PrintHeaders ?? false, TableStyle);
return LoadFromDictionaries(Collection as IEnumerable<IDictionary<string, object>>, PrintHeaders ?? false, TableStyle, Members.Select(x => x.Name));
}
var param = new LoadFromCollectionParams
{
Expand Down Expand Up @@ -365,8 +365,8 @@ public ExcelRangeBase LoadFromCollection<T>(IEnumerable<T> collection, Action<Lo
if (collection is IEnumerable<IDictionary<string, object>>)
{
if (param.Members == null)
return LoadFromDictionaries(collection as IEnumerable<IDictionary<string, object>>, param.PrintHeaders, param.TableStyle);
return LoadFromDictionaries(collection as IEnumerable<IDictionary<string, object>>, param.PrintHeaders, param.TableStyle, param.Members.Select(x => x.Name));
return LoadFromDictionaries(collection as IEnumerable<IDictionary<string, object>>, param.PrintHeaders ?? false, param.TableStyle);
return LoadFromDictionaries(collection as IEnumerable<IDictionary<string, object>>, param.PrintHeaders ?? false, param.TableStyle, param.Members.Select(x => x.Name));
}
var func = new LoadFromCollection<T>(this, collection, param);
return func.Load();
Expand Down
19 changes: 19 additions & 0 deletions src/EPPlus/FormulaParsing/Excel/Functions/FunctionRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,24 @@ public static FunctionRepository Create()
/// </summary>
/// <param name="module">A <see cref="IFunctionModule"/> that can be used for adding functions and custom function compilers.</param>
public virtual void LoadModule(IFunctionModule module)
{
LoadModule(module, false);
}

/// <summary>
/// Loads a module of <see cref="ExcelFunction"/>s to the function repository.
/// </summary>
/// <param name="module">A <see cref="IFunctionModule"/> that can be used for adding functions and custom function compilers.</param>
/// <param name="replaceExistingFunction">If true, any existing function will be replaced by the function from the supplied module</param>
public virtual void LoadModule(IFunctionModule module, bool replaceExistingFunction)
{
foreach (var key in module.Functions.Keys)
{
var lowerKey = key.ToLower(CultureInfo.InvariantCulture);
if(replaceExistingFunction && _functions.ContainsKey(lowerKey))
{
_functions.Remove(lowerKey);
}
_functions[lowerKey] = module.Functions[key];
}
foreach (var key in module.CustomCompilers.Keys)
Expand All @@ -68,6 +82,11 @@ public virtual void LoadModule(IFunctionModule module)
}
}

/// <summary>
/// Returns a function by its name
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public virtual ExcelFunction GetFunction(string name)
{
if(!_functions.ContainsKey(name.ToLower(CultureInfo.InvariantCulture)))
Expand Down
2 changes: 2 additions & 0 deletions src/EPPlus/LoadFunctions/ColumnInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public ColumnInfo()

internal string Path { get; set; }

internal bool IsNestedClass { get; set; }

public override string ToString()
{
if(!string.IsNullOrEmpty(Header))
Expand Down
35 changes: 12 additions & 23 deletions src/EPPlus/LoadFunctions/LoadFromCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,40 +46,29 @@ public LoadFromCollection(ExcelRangeBase range, IEnumerable<T> items, LoadFromCo
{
SortOrderProperties = classSortOrderAttr.Properties.ToList();
}
LoadFromCollectionColumns<T> cols;
if (parameters.Members == null)
{
var cols = new LoadFromCollectionColumns<T>(parameters.BindingFlags, SortOrderProperties);
cols = new LoadFromCollectionColumns<T>(parameters.BindingFlags, SortOrderProperties);
var columns = cols.Setup();
_columns = columns.ToArray();
SetHiddenColumns();
}
else
{
_columns = parameters.Members.Select(x => new ColumnInfo { MemberInfo = x }).ToArray();
if (_columns.Length == 0) //Fixes issue 15555
if (parameters.Members.Length == 0) //Fixes issue 15555
{
throw (new ArgumentException("Parameter Members must have at least one property. Length is zero"));
}
var colIx = 0;
foreach (var columnInfo in _columns)
cols = new LoadFromCollectionColumns<T>(parameters.BindingFlags, SortOrderProperties, parameters.Members);
var columns = cols.Setup();
_columns = columns.ToArray();
// the ValidateType method will throw an InvalidCastException
// if parameters.Members contains a MemberInfo that is not declared
// by any of the types used.
foreach(var member in parameters.Members )
{
if (columnInfo.MemberInfo == null) continue;
if(columnInfo.Hidden)
{
Range.Worksheet.Column(Range._fromCol + colIx).Hidden = true;
}
colIx++;
var member = columnInfo.MemberInfo;
if (member.DeclaringType != null && member.DeclaringType != type)
{
_isSameType = false;
}

//Fixing inverted check for IsSubclassOf / Pullrequest from tom dam
if (member.DeclaringType != null && member.DeclaringType != type && !TypeCompat.IsSubclassOf(type, member.DeclaringType) && !TypeCompat.IsSubclassOf(member.DeclaringType, type))
{
throw new InvalidCastException("Supplied properties in parameter Properties must be of the same type as T (or an assignable type from T)");
}
cols.ValidateType(member);
}
}
}
Expand Down Expand Up @@ -145,7 +134,7 @@ protected override void LoadInternal(object[,] values, out Dictionary<int, Formu
int col = 0, row = 0;
columnFormats = new Dictionary<int, string>();
formulaCells = new Dictionary<int, FormulaCell>();
if (_columns.Length > 0 && PrintHeaders)
if (_columns.Length > 0 && (PrintHeaders ?? false))
{
SetHeaders(values, columnFormats, ref col, ref row);
}
Expand Down
66 changes: 61 additions & 5 deletions src/EPPlus/LoadFunctions/LoadFromCollectionColumns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
08/286/2021 EPPlus Software AB EPPlus 5.7.5
*************************************************************************************************/
using OfficeOpenXml.Attributes;
using OfficeOpenXml.Compatibility;
using OfficeOpenXml.Table;
using OfficeOpenXml.Utils;
using System;
Expand All @@ -30,15 +31,52 @@ public LoadFromCollectionColumns(BindingFlags bindingFlags)
}

public LoadFromCollectionColumns(BindingFlags bindingFlags, List<string> sortOrderColumns)
: this(bindingFlags, sortOrderColumns, null)
{

}

public LoadFromCollectionColumns(BindingFlags bindingFlags, List<string> sortOrderColumns, MemberInfo[] members)
{
_bindingFlags = bindingFlags;
_sortOrderColumns = sortOrderColumns;
_includedTypes = new HashSet<Type>
{
typeof(T)
};
if (members != null && members.Length > 0)
{
_members = new HashSet<MemberInfo>(members);
}
else
{
_members = new HashSet<MemberInfo>();
}
}

private readonly BindingFlags _bindingFlags;
private readonly List<string> _sortOrderColumns;
private readonly HashSet<MemberInfo> _members;
private readonly HashSet<Type> _includedTypes;
private const int SortOrderOffset = ExcelPackage.MaxColumns;

internal void ValidateType(MemberInfo member)
{
var isValid = false;
foreach(var includedType in _includedTypes)
{

if(member.DeclaringType == includedType
|| member.DeclaringType.IsAssignableFrom(includedType)
|| member.DeclaringType.IsSubclassOf(includedType))
{
isValid = true;
break;
}
}
if(!isValid) throw new InvalidCastException("Supplied properties in parameter Properties must be of the same type as T (or an assignable type from T)");
}

internal List<ColumnInfo> Setup()
{
var result = new List<ColumnInfo>();
Expand All @@ -65,7 +103,15 @@ private List<ListType> CopyList<ListType>(List<ListType> source)
return copy;
}

private bool SetupInternal(Type type, List<ColumnInfo> result, List<int> sortOrderListArg, string path = null, string headerPrefix = null)
private bool ShouldIgnoreMember(MemberInfo member)
{
if (member == null) return true;
if (member.HasPropertyOfType<EpplusIgnore>()) return true;
if(_members.Count == 0) return false;
return !_members.Contains(member);
}

private bool SetupInternal(Type type, List<ColumnInfo> result, List<int> sortOrderListArg, bool isNestedClass = false, string path = null, string headerPrefix = null)
{
var sort = false;
var members = type.GetProperties(_bindingFlags);
Expand All @@ -77,7 +123,7 @@ private bool SetupInternal(Type type, List<ColumnInfo> result, List<int> sortOrd
{
var hPrefix = default(string);
var sortOrderList = CopyList(sortOrderListArg);
if (member.HasPropertyOfType<EpplusIgnore>())
if (ShouldIgnoreMember(member))
{
continue;
}
Expand Down Expand Up @@ -122,7 +168,7 @@ private bool SetupInternal(Type type, List<ColumnInfo> result, List<int> sortOrd
sortOrderList[0] = _sortOrderColumns.IndexOf(memberPath);
}
}
SetupInternal(member.PropertyType, result, sortOrderList, memberPath, hPrefix);
SetupInternal(member.PropertyType, result, sortOrderList, true, memberPath, hPrefix);
sortOrderList.RemoveAt(sortOrderList.Count - 1);
continue;
}
Expand Down Expand Up @@ -174,6 +220,10 @@ private bool SetupInternal(Type type, List<ColumnInfo> result, List<int> sortOrd
{
header = string.IsNullOrEmpty(header) ? member.Name : header;
}
if(!_includedTypes.Contains(member.DeclaringType))
{
_includedTypes.Add(member.DeclaringType);
}
result.Add(new ColumnInfo
{
Header = header,
Expand All @@ -187,7 +237,8 @@ private bool SetupInternal(Type type, List<ColumnInfo> result, List<int> sortOrd
TotalsRowNumberFormat = totalsRowNumberFormat,
TotalsRowLabel = totalsRowLabel,
TotalsRowFormula = totalsRowFormula,
Path = memberPath
Path = memberPath,
IsNestedClass = isNestedClass
});
}
}
Expand Down Expand Up @@ -235,13 +286,18 @@ private bool SetupInternal(Type type, List<ColumnInfo> result, List<int> sortOrd
{
h = member.Name;
}
if (!_includedTypes.Contains(member.DeclaringType))
{
_includedTypes.Add(member.DeclaringType);
}
return new ColumnInfo {
Index = index++,
MemberInfo = member,
Path = mp,
Header = h,
SortOrder = sortOrder,
SortOrderLevels = colInfoSortOrderList
SortOrderLevels = colInfoSortOrderList,
IsNestedClass = isNestedClass
};
}));
}
Expand Down
2 changes: 1 addition & 1 deletion src/EPPlus/LoadFunctions/LoadFromDictionaries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ protected override void LoadInternal(object[,] values, out Dictionary<int, Formu
columnFormats = new Dictionary<int, string>();
formulaCells = new Dictionary<int, FormulaCell>();
int col = 0, row = 0;
if (PrintHeaders && _keys.Count() > 0)
if (PrintHeaders ?? false && _keys.Count() > 0)
{
foreach (var key in _keys)
{
Expand Down
10 changes: 5 additions & 5 deletions src/EPPlus/LoadFunctions/LoadFunctionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public LoadFunctionBase(ExcelRangeBase range, LoadFunctionFunctionParamsBase par
/// <summary>
/// If true a header row will be printed above the data
/// </summary>
protected bool PrintHeaders { get; }
protected bool? PrintHeaders { get; }

/// <summary>
/// If value is other than TableStyles.None the data will be added to a table in the worksheet.
Expand Down Expand Up @@ -78,7 +78,7 @@ protected virtual void PostProcessTable(ExcelTable table, ExcelRangeBase range)
/// <returns></returns>
internal ExcelRangeBase Load()
{
var nRows = PrintHeaders ? GetNumberOfRows() + 1 : GetNumberOfRows();
var nRows = PrintHeaders ?? false ? GetNumberOfRows() + 1 : GetNumberOfRows();
var nCols = GetNumberOfColumns();
var values = new object[nRows, nCols];

Expand All @@ -100,7 +100,7 @@ internal ExcelRangeBase Load()


//Must have at least 1 row, if header is shown
if (nRows == 1 && PrintHeaders)
if (nRows == 1 && (PrintHeaders ?? false))
{
nRows++;
}
Expand All @@ -120,7 +120,7 @@ internal ExcelRangeBase Load()
if (TableStyle.HasValue)
{
var tbl = ws.Tables.Add(r, TableName);
tbl.ShowHeader = PrintHeaders;
tbl.ShowHeader = PrintHeaders ?? false;
tbl.TableStyle = TableStyle.Value;
tbl.ShowFirstColumn = ShowFirstColumn;
tbl.ShowLastColumn = ShowLastColumn;
Expand All @@ -137,7 +137,7 @@ private void SetValuesAndFormulas(int nRows, int nCols, object[,] values, Dictio
if (formulaCells.ContainsKey(col))
{
var row = 0;
if (PrintHeaders)
if (PrintHeaders ?? false)
{
var header = values[0, col];
ws.SetValue(Range._fromRow, Range._fromCol + col, header);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public abstract class LoadFunctionFunctionParamsBase
/// <summary>
/// If true a row with headers will be added above the data
/// </summary>
public bool PrintHeaders
public bool? PrintHeaders
{
get; set;
}
Expand Down
Loading

0 comments on commit 525e0d2

Please sign in to comment.