Skip to content

Commit

Permalink
#969, #1058 - added support for Dictionary property in LoadFromCollec…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
swmal committed Nov 9, 2023
1 parent f11b939 commit a7c1806
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 28 deletions.
2 changes: 2 additions & 0 deletions src/EPPlus/Attributes/EPPlusDictionaryColumnAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ public int Order
/// The values of this array will be used to generate columns (one column for each item).
/// </summary>
public string[] ColumnHeaders { get; set; }

public string HeadersKey { get; set; }
}
}
34 changes: 34 additions & 0 deletions src/EPPlus/LoadFunctions/IDictionaryKeysProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*************************************************************************************************
Required Notice: Copyright (C) EPPlus Software AB.
This software is licensed under PolyForm Noncommercial License 1.0.0
and may only be used for noncommercial purposes
https://polyformproject.org/licenses/noncommercial/1.0.0/
A commercial license to use this software can be purchased at https://epplussoftware.com
*************************************************************************************************
Date Author Change
*************************************************************************************************
7/11/2023 EPPlus Software AB EPPlus 7
*************************************************************************************************/
using OfficeOpenXml.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OfficeOpenXml.LoadFunctions
{
/// <summary>
/// Provides keys of a property decorated with the <see cref="EPPlusDictionaryColumnAttribute"/>
/// </summary>
public interface IDictionaryKeysProvider
{
/// <summary>
/// This function will return keys that will be used as column headers
/// based on the <paramref name="key"/> that will be read from the <see cref="EPPlusDictionaryColumnAttribute"/>
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public IEnumerable<string> GetKeys(string key);
}
}
4 changes: 2 additions & 2 deletions src/EPPlus/LoadFunctions/LoadFromCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public LoadFromCollection(ExcelRangeBase range, IEnumerable<T> items, LoadFromCo
LoadFromCollectionColumns<T> cols;
if (parameters.Members == null)
{
cols = new LoadFromCollectionColumns<T>(parameters.BindingFlags, SortOrderProperties);
cols = new LoadFromCollectionColumns<T>(parameters, SortOrderProperties);
var columns = cols.Setup();
_columns = columns.ToArray();
SetHiddenColumns();
Expand All @@ -64,7 +64,7 @@ public LoadFromCollection(ExcelRangeBase range, IEnumerable<T> items, LoadFromCo
{
throw (new ArgumentException("Parameter Members must have at least one property. Length is zero"));
}
cols = new LoadFromCollectionColumns<T>(parameters.BindingFlags, SortOrderProperties, parameters.Members);
cols = new LoadFromCollectionColumns<T>(parameters, SortOrderProperties);
var columns = cols.Setup();
_columns = columns.ToArray();
// the ValidateType method will throw an InvalidCastException
Expand Down
36 changes: 20 additions & 16 deletions src/EPPlus/LoadFunctions/LoadFromCollectionColumns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Date Author Change
08/286/2021 EPPlus Software AB EPPlus 5.7.5
*************************************************************************************************/
using OfficeOpenXml.Attributes;
using OfficeOpenXml.LoadFunctions.Params;
using OfficeOpenXml.Table;
using OfficeOpenXml.Utils;
using System;
Expand All @@ -23,27 +24,21 @@ namespace OfficeOpenXml.LoadFunctions
{
internal class LoadFromCollectionColumns<T>
{
public LoadFromCollectionColumns(BindingFlags bindingFlags)
: this(bindingFlags, new List<string>())
{

}

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

}
public LoadFromCollectionColumns(LoadFromCollectionParams parameters):
this(parameters, Enumerable.Empty<string>().ToList())
{ }

public LoadFromCollectionColumns(BindingFlags bindingFlags, List<string> sortOrderColumns, MemberInfo[] members)
public LoadFromCollectionColumns(LoadFromCollectionParams parameters, List<string> sortOrderColumns)
{
_bindingFlags = bindingFlags;
_bindingFlags = parameters.BindingFlags;
_sortOrderColumns = sortOrderColumns;
_filterMembers = members;
_filterMembers = parameters.Members;
_keysProvider = parameters.KeysProvider;
_includedTypes = new HashSet<Type>
{
typeof(T)
};
var members = parameters.Members;
_members = new Dictionary<Type, HashSet<string>>();
if (members != null && members.Length > 0)
{
Expand All @@ -58,6 +53,7 @@ public LoadFromCollectionColumns(BindingFlags bindingFlags, List<string> sortOrd
private readonly BindingFlags _bindingFlags;
private readonly List<string> _sortOrderColumns;
private readonly Dictionary<Type, HashSet<string>> _members;
private readonly IDictionaryKeysProvider _keysProvider;
private MemberInfo[] _filterMembers;
private readonly HashSet<Type> _includedTypes;
private const int SortOrderOffset = ExcelPackage.MaxColumns;
Expand Down Expand Up @@ -246,7 +242,16 @@ private void HandleDictionaryColumnsAttribute(List<ColumnInfo> result, MemberInf
}
var sortOrderColumnsIndex = _sortOrderColumns != null ? _sortOrderColumns.IndexOf(memberPath) : -1;
var so = sortOrderColumnsIndex > -1 ? sortOrderColumnsIndex : attr.Order + SortOrderOffset;
foreach (var key in attr.ColumnHeaders)
var columnHeaders = Enumerable.Empty<string>();
if(!string.IsNullOrEmpty(attr.HeadersKey) && _keysProvider != null)
{
columnHeaders = _keysProvider.GetKeys(attr.HeadersKey);
}
else if(attr.ColumnHeaders != null && attr.ColumnHeaders.Length > 0)
{
columnHeaders = attr.ColumnHeaders;
}
foreach (var key in columnHeaders)
{
result.Add(new ColumnInfo
{
Expand All @@ -257,7 +262,6 @@ private void HandleDictionaryColumnsAttribute(List<ColumnInfo> result, MemberInf
Path = memberPath,
Header = key,
SortOrder = so
//SortOrderLevels = colInfoSortOrderList
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,10 @@ public bool UseBuiltInStylesForHyperlinks
get;
set;
} = true;

/// <summary>
/// A custom class that implements the <see cref="IDictionaryKeysProvider"/>
/// </summary>
public IDictionaryKeysProvider KeysProvider { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ public void Cleanup()
[TestMethod]
public void ShouldSetupColumnsWithPath()
{
var cols = new LoadFromCollectionColumns<Outer>(LoadFromCollectionParams.DefaultBindingFlags, Enumerable.Empty<string>().ToList());
var parameters = new LoadFromCollectionParams
{
BindingFlags = LoadFromCollectionParams.DefaultBindingFlags
};
var cols = new LoadFromCollectionColumns<Outer>(parameters);
var result = cols.Setup();
Assert.AreEqual(5, result.Count, "List did not contain 5 elements as expected");
Assert.AreEqual("ApprovedUtc", result[0].Path);
Expand All @@ -88,7 +92,11 @@ public void ShouldSetupColumnsWithPath()
[TestMethod]
public void ShouldSetupColumnsWithPathSorted()
{
var cols = new LoadFromCollectionColumns<OuterReversedSortOrder>(LoadFromCollectionParams.DefaultBindingFlags);
var parameters = new LoadFromCollectionParams
{
BindingFlags = LoadFromCollectionParams.DefaultBindingFlags
};
var cols = new LoadFromCollectionColumns<OuterReversedSortOrder>(parameters);
var result = cols.Setup();
Assert.AreEqual(5, result.Count, "List did not contain 5 elements as expected");
Assert.AreEqual("Acknowledged", result[0].Path);
Expand All @@ -105,7 +113,8 @@ public void ShouldSetupColumnsWithPathSortedByClassAttribute()
"Acknowledged",
"Organization.OrgLevel5"
};
var cols = new LoadFromCollectionColumns<OuterReversedSortOrder>(LoadFromCollectionParams.DefaultBindingFlags, order);
var parameters = new LoadFromCollectionParams { BindingFlags = LoadFromCollectionParams.DefaultBindingFlags };
var cols = new LoadFromCollectionColumns<OuterReversedSortOrder>(parameters, order);
var result = cols.Setup();
Assert.AreEqual(5, result.Count, "List did not contain 5 elements as expected");
Assert.AreEqual("ApprovedUtc", result[0].Path);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OfficeOpenXml;
using OfficeOpenXml.Attributes;
using OfficeOpenXml.LoadFunctions;
using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions;
using System;
using System.Collections.Generic;
Expand All @@ -15,11 +16,33 @@ public class LoadFromCollectionDictionaryPropertyTests
[EpplusTable]
public class TestClass
{
[EpplusTableColumn(Order = 2)]
[EpplusTableColumn(Order = 3)]
public string Name { get; set; }

[EPPlusDictionaryColumn(Order = 1, ColumnHeaders = new string[] { "A", "B", "C"})]
public Dictionary<string, object> Columns { get; set; }
[EPPlusDictionaryColumn(Order = 2, HeadersKey = "1")]
public Dictionary<string, object> Columns { get; set; }
}

public class TestClass2 : TestClass
{
[EPPlusDictionaryColumn(Order = 1, HeadersKey = "2")]
public Dictionary<string, object> Columns2 { get; set; }
}

public class MyKeysProvider : IDictionaryKeysProvider
{
public IEnumerable<string> GetKeys(string key)
{
switch(key)
{
case "1":
return new string[] { "A", "B", "C" };
case "2":
return new string[] { "C", "D", "E" };
default:
return Enumerable.Empty<string>();
}
}
}

[TestMethod]
Expand All @@ -34,17 +57,54 @@ public void ShouldReadColumnsAndValuesFromDictionaryProperty()
using(var package = new ExcelPackage())
{
var sheet = package.Workbook.Worksheets.Add("test");
sheet.Cells["A1"].LoadFromCollection(items, c => c.PrintHeaders = true);
Assert.AreEqual("A", sheet.Cells["A1"].Value);
Assert.AreEqual("B", sheet.Cells["B1"].Value);
Assert.AreEqual("C", sheet.Cells["C1"].Value);
sheet.Cells["A1"].LoadFromCollection(items, c =>
{
c.PrintHeaders = true;
c.KeysProvider = new MyKeysProvider();
});
Assert.AreEqual("C", sheet.Cells["A1"].Value);
Assert.AreEqual("D", sheet.Cells["B1"].Value);
Assert.AreEqual("E", sheet.Cells["C1"].Value);
Assert.AreEqual("Name", sheet.Cells["D1"].Value);
Assert.AreEqual(1, sheet.Cells["A2"].Value);
Assert.AreEqual(2, sheet.Cells["B2"].Value);
Assert.IsNull(sheet.Cells["C2"].Value);
Assert.AreEqual("test 1", sheet.Cells["D2"].Value);
}
}

[TestMethod]
public void ShouldReadColumnsAndValuesFromDictionaryProperty2()
{
var item1 = new TestClass2
{
Name = "test 1",
Columns = new Dictionary<string, object> { { "A", 3 } },
Columns2 = new Dictionary<string, object> { { "C", 1 }, { "D", 2 } }
};
var items = new List<TestClass2> { item1 };
using (var package = new ExcelPackage())
{
var sheet = package.Workbook.Worksheets.Add("test");
sheet.Cells["A1"].LoadFromCollection(items, c =>
{
c.PrintHeaders = true;
c.KeysProvider = new MyKeysProvider();
});
Assert.AreEqual("C", sheet.Cells["A1"].Value);
Assert.AreEqual("D", sheet.Cells["B1"].Value);
Assert.AreEqual("E", sheet.Cells["C1"].Value);
Assert.AreEqual("A", sheet.Cells["D1"].Value);
Assert.AreEqual("B", sheet.Cells["E1"].Value);
Assert.AreEqual("C", sheet.Cells["F1"].Value);
Assert.AreEqual("Name", sheet.Cells["G1"].Value);
Assert.AreEqual(1, sheet.Cells["A2"].Value);
Assert.AreEqual(2, sheet.Cells["B2"].Value);
Assert.IsNull(sheet.Cells["C2"].Value);
Assert.AreEqual(2, sheet.Cells["D2"].Value);
Assert.IsNull(sheet.Cells["E2"].Value);
Assert.AreEqual("test 1", sheet.Cells["G2"].Value);
}
}
}
}

0 comments on commit a7c1806

Please sign in to comment.