Skip to content

Commit 73dfb6a

Browse files
authored
Merge pull request #4 from hurles/release/2.0.0
Release 2.0.0 into main
2 parents a0e9862 + 2d2abf2 commit 73dfb6a

14 files changed

Lines changed: 323 additions & 90 deletions

File tree

SharpExcel.Models/Configuration/ExporterOptions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using SharpExcel.Models.Data;
12
using SharpExcel.Models.Styling;
23
using SharpExcel.Models.Styling.Rules;
34

@@ -15,6 +16,11 @@ public class ExporterOptions<TExportModel>
1516
/// </summary>
1617
public StylingCollection<TExportModel> Styling { get; set; } = new();
1718

19+
/// <summary>
20+
/// Targeting collection
21+
/// </summary>
22+
public TargetingCollection<TExportModel> Targeting { get; set; } = new();
23+
1824
/// <summary>
1925
/// Fluent method to set default header style for this exporter
2026
/// </summary>
@@ -60,4 +66,17 @@ public ExporterOptions<TExportModel> WithStylingRule(Action<StylingRule<TExportM
6066
Styling.Rules.Add(stylingRule);
6167
return this;
6268
}
69+
70+
/// <summary>
71+
/// Fluent method to add a styling rule for this exporter
72+
/// </summary>
73+
/// <param name="stylingRuleOptions">constructs the styling rule</param>
74+
/// <returns></returns>
75+
public ExporterOptions<TExportModel> WithTarget(Action<TargetingRule<TExportModel>> targetingRuleOptions)
76+
{
77+
var stylingRule = new TargetingRule<TExportModel>();
78+
targetingRuleOptions(stylingRule);
79+
Targeting.Rules.Add(stylingRule);
80+
return this;
81+
}
6382
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace SharpExcel.Models.Data;
2+
3+
public class TargetingCollection<TExportModel>
4+
{
5+
public List<TargetingRule<TExportModel>> Rules { get; set; } = new();
6+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace SharpExcel.Models.Data;
4+
5+
public interface ITargetingRule
6+
{
7+
8+
}
9+
public record TargetingRule<TRecord> : ITargetingRule
10+
{
11+
/// <summary>
12+
/// REQUIRED: name of the sheet in the excel file
13+
/// </summary>
14+
[MinLength(1)]
15+
public string SheetName { get; set; } = null!;
16+
17+
/// <summary>
18+
/// Optional Row to start reading/writing from.
19+
/// This is useful when you want to only affect part of a sheet.
20+
/// Excel rows start as 1, so 1 is the first row
21+
/// </summary>
22+
public int? Row { get; set; }
23+
24+
/// <summary>
25+
/// Optional Column to start reading/writing from.
26+
/// This is useful when you want to only affect part of a sheet.
27+
/// Excel columns start as 1, so 1 is the first row
28+
/// </summary>
29+
public int? Column { get; set; }
30+
31+
/// <summary>
32+
/// Conditions to check if the rule should be applied.
33+
/// </summary>
34+
public Func<TRecord, bool>? RulePredicate { get; set; }
35+
36+
public TargetingRule<TRecord> WithCondition(Func<TRecord, bool> condition)
37+
{
38+
RulePredicate = condition;
39+
//return this object so we can chain calls
40+
return this;
41+
}
42+
43+
/// <summary>
44+
/// Sets the row to start reading/writing from.
45+
/// </summary>
46+
/// <param name="rowId"></param>
47+
/// <returns></returns>
48+
public TargetingRule<TRecord> WithStartRow(int rowId)
49+
{
50+
Row = rowId;
51+
//return this object so we can chain calls
52+
return this;
53+
}
54+
55+
/// <summary>
56+
/// Sets the column to start reading/writing from.
57+
/// </summary>
58+
public TargetingRule<TRecord> WithStartColumn(int columnId)
59+
{
60+
Column = columnId;
61+
//return this object so we can chain calls
62+
return this;
63+
}
64+
65+
/// <summary>
66+
/// Sets the sheet name to start reading/writing from.
67+
/// </summary>
68+
public TargetingRule<TRecord> WithSheetName(string sheetName)
69+
{
70+
SheetName = sheetName;
71+
//return this object so we can chain calls
72+
return this;
73+
}
74+
}
75+
76+

SharpExcel.Models/Results/ExcelAddress.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ public record struct ExcelAddress
2424
/// Header name
2525
/// </summary>
2626
public string? HeaderName { get; set; }
27+
28+
/// <summary>
29+
/// Sheet Name
30+
/// </summary>
31+
public string SheetName { get; set; }
2732
}

SharpExcel.Models/Results/ExcelReadResult.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,25 @@ public class ExcelReadResult<TModel>
66
public List<TModel> Records { get; set; } = new();
77

88
public Dictionary<TModel, ExcelCellValidationResult> ValidationResults { get; set; } = new();
9+
10+
11+
}
12+
13+
public static class ExcelReadResultExtensions
14+
{
15+
public static void Append<TModel>(this ExcelReadResult<TModel> result, ExcelReadResult<TModel> other)
16+
where TModel : class
17+
{
18+
result.Records.AddRange(other.Records);
19+
foreach (var kvp in other.ValidationResults)
20+
{
21+
if (!result.ValidationResults.ContainsKey(kvp.Key))
22+
{
23+
result.ValidationResults.Add(kvp.Key, kvp.Value);
24+
continue;
25+
}
26+
result.ValidationResults[kvp.Key] = kvp.Value;
27+
}
28+
29+
}
930
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using SharpExcel.Models.Data;
2+
3+
namespace SharpExcel.Models.Styling.Constants;
4+
5+
public class ExcelTargetingConstants<TModel>
6+
where TModel : class
7+
{
8+
public static TargetingRule<TModel> DefaultTargetingRule = new TargetingRule<TModel>
9+
{
10+
SheetName = "Export",
11+
Column = 1,
12+
Row = 1,
13+
RulePredicate = _ => true,
14+
};
15+
16+
}

SharpExcel.TestApplication/Program.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Globalization;
2-
using SharpExcel.Models.Arguments;
32
using SharpExcel.Models.Results;
43
using SharpExcel.Models.Styling.Colorization;
54
using SharpExcel.TestApplication.TestData;
@@ -48,6 +47,23 @@
4847
//can be omitted to use default style
4948
rule.WhenFalse(ExcelCellStyleConstants.DefaultDataStyle.WithTextColor(new(80, 160, 80)));
5049
});
50+
51+
options.WithTarget(rule =>
52+
{
53+
rule.WithCondition(x => x.Status != TestStatus.Fired);
54+
rule.WithSheetName("Employees");
55+
rule.WithStartColumn(3);
56+
rule.WithStartRow(3);
57+
});
58+
59+
options.WithTarget(rule =>
60+
{
61+
rule.WithCondition(x => x.Status == TestStatus.Fired);
62+
rule.WithSheetName("Fired");
63+
rule.WithStartRow(3);
64+
rule.WithStartColumn(3);
65+
66+
});
5167
});
5268

5369
using IHost host = builder.Build();
@@ -60,20 +76,14 @@ async Task RunApp(IServiceProvider services)
6076
var exportPath = $"./OutputFolder/TestExport-{Guid.NewGuid()}.xlsx";
6177
var validationExportPath = $"./OutputFolder/ErrorChecked-{Guid.NewGuid()}.xlsx";
6278
var exportService = services.GetRequiredService<ISharpExcelSynchronizer<TestExportModel>>();
63-
64-
var excelArguments = new ExcelArguments()
65-
{
66-
SheetName = "Budgets",
67-
CultureInfo = CultureInfo.CurrentCulture
68-
};
6979

70-
using var workbook = await exportService.GenerateWorkbookAsync(excelArguments, TestDataProvider.GetTestData());
80+
using var workbook = await exportService.GenerateWorkbookAsync(CultureInfo.CurrentCulture, TestDataProvider.GetTestData());
7181
workbook.SaveAs(exportPath);
7282

73-
using var errorCheckedWorkbook = await exportService.ValidateAndAnnotateWorkbookAsync(excelArguments, workbook);
83+
using var errorCheckedWorkbook = await exportService.ValidateAndAnnotateWorkbookAsync(CultureInfo.CurrentCulture, workbook);
7484
errorCheckedWorkbook.SaveAs(validationExportPath);
7585

76-
var importedWorkbook = await exportService.ReadWorkbookAsync(excelArguments, workbook);
86+
var importedWorkbook = await exportService.ReadWorkbookAsync(CultureInfo.CurrentCulture, workbook);
7787

7888
#region write_output
7989
foreach (var dataItem in importedWorkbook.Records)

SharpExcel.TestApplication/TestData/TestDataProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public static List<TestExportModel> GetTestData()
99
new() { Id = 0, FirstName = "John", LastName = "Doe", Budget = 2400.34m, Email = "john.doe@example.com", TestDepartment = TestDepartment.Unknown, Status = TestStatus.Employed },
1010
new() { Id = 1, FirstName = "Jane", LastName = "Doe", Budget = -200.42m, Email = "jane.doe@example.com", TestDepartment = TestDepartment.ValueB, Status = TestStatus.Fired },
1111
new() { Id = 2, FirstName = "John", LastName = "Neutron", Budget = 0.0m, Email = null, TestDepartment = TestDepartment.ValueB, Status = TestStatus.Employed },
12-
new() { Id = 3, FirstName = "Ash", LastName = "Ketchum", Budget = 69m, Email = "ash@example.com", TestDepartment = TestDepartment.ValueC, Status = TestStatus.Fired },
12+
new() { Id = 3, FirstName = "Ash", LastName = "Ketchum", Budget = 69m, Email = null, TestDepartment = TestDepartment.ValueC, Status = TestStatus.Fired },
1313
new() { Id = 4, FirstName = "Inspector", LastName = "Gadget", Budget = 1337m, Email = "gogogadget@example.com", TestDepartment = TestDepartment.ValueC, Status = TestStatus.Employed },
1414
new() { Id = 5, FirstName = "Mindy", LastName = "", Budget = 2400.34m, Email = "mmouse@example.com", TestDepartment = TestDepartment.ValueA, Status = TestStatus.Employed },
1515
new() { Id = 6, FirstName = "ThisIsLongerThan10", LastName = "Mouse", Budget = 2400.34m, Email = "mmouse@example.com", TestDepartment = TestDepartment.ValueA, Status = TestStatus.Employed },

SharpExcel.Tests/ExcelImportTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
using System.Globalization;
12
using ClosedXML.Excel;
23
using Microsoft.Extensions.Options;
34
using SharpExcel.Tests.Shared;
4-
using SharpExcel.Models.Arguments;
55
using SharpExcel.Models.Configuration.Constants;
6+
using SharpExcel.Models.Styling.Constants;
67
using Shouldly;
78
using Xunit;
89

@@ -23,15 +24,15 @@ public ExcelImportTests()
2324
[Fact]
2425
public async Task CreateWorkbookTest()
2526
{
26-
var workbook = await _synchronizer.GenerateWorkbookAsync(new ExcelArguments(){ SheetName = "TestSheet"}, CreateTestData());
27-
workbook.Worksheets.FirstOrDefault(x => x.Name == "TestSheet").ShouldNotBeNull();
27+
var workbook = await _synchronizer.GenerateWorkbookAsync(CultureInfo.CurrentCulture, CreateTestData());
28+
workbook.Worksheets.FirstOrDefault(x => x.Name == ExcelTargetingConstants<TestModel>.DefaultTargetingRule.SheetName).ShouldNotBeNull();
2829

2930
workbook.ShouldNotBeNull();
3031
//there should be 2 worksheets, a visible one for the data, and a hidden one to pull data from for the enum dropdowns
3132
workbook.Worksheets.Count.ShouldBe(2);
3233

3334
//main data worksheet
34-
workbook.Worksheet(1).Name.ShouldBe("TestSheet");
35+
workbook.Worksheet(1).Name.ShouldBe(ExcelTargetingConstants<TestModel>.DefaultTargetingRule.SheetName);
3536
workbook.Worksheet(1).Visibility.ShouldBe(XLWorksheetVisibility.Visible);
3637

3738
//hidden worksheet for enum dropdowns
@@ -41,12 +42,11 @@ public async Task CreateWorkbookTest()
4142
[Fact]
4243
public async Task ReadWorkbookTest()
4344
{
44-
var args = new ExcelArguments() { SheetName = "TestSheet" };
4545
//create test workbook
46-
var workbook = await _synchronizer.GenerateWorkbookAsync( args, CreateTestData());
46+
var workbook = await _synchronizer.GenerateWorkbookAsync(CultureInfo.InvariantCulture, CreateTestData());
4747

4848
//read workbook
49-
var output = await _synchronizer.ReadWorkbookAsync(args, workbook);
49+
var output = await _synchronizer.ReadWorkbookAsync(CultureInfo.InvariantCulture, workbook);
5050

5151

5252
output.Records.Count.ShouldBe(2);

SharpExcel/Abstraction/ISharpExcelSynchronizer.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using ClosedXML.Excel;
2-
using SharpExcel.Models.Arguments;
1+
using System.Globalization;
2+
using ClosedXML.Excel;
33
using SharpExcel.Models.Results;
44

55
namespace SharpExcel.Abstraction;
@@ -14,10 +14,10 @@ public interface ISharpExcelSynchronizer<TModel>
1414
/// <summary>
1515
/// Generates a workbook based on the provided data
1616
/// </summary>
17-
/// <param name="arguments">Collection of arguments</param>
17+
/// <param name="cultureInfo"></param>
1818
/// <param name="data">The data to generate the workbook from</param>
1919
/// <returns></returns>
20-
public Task<XLWorkbook> GenerateWorkbookAsync(ExcelArguments arguments, IEnumerable<TModel> data);
20+
public Task<XLWorkbook> GenerateWorkbookAsync(CultureInfo cultureInfo, ICollection<TModel> data);
2121

2222
/// <summary>
2323
/// Reads a workbook to convert it into the given model
@@ -26,13 +26,13 @@ public interface ISharpExcelSynchronizer<TModel>
2626
/// <param name="workbook"></param>
2727
/// <typeparam name="TModel"></typeparam>
2828
/// <returns></returns>
29-
public Task<ExcelReadResult<TModel>> ReadWorkbookAsync(ExcelArguments arguments, XLWorkbook workbook);
29+
public Task<ExcelReadResult<TModel>> ReadWorkbookAsync(CultureInfo arguments, XLWorkbook workbook);
3030

3131
/// <summary>
3232
/// Reads, then returns the supplied workbook, but highlights cells containing invalid data, using standard System.ComponentModel.DataAnnotations validation on the model
3333
/// </summary>
34-
/// <param name="arguments">Collection of arguments</param>
34+
/// <param name="cultureInfo"></param>
3535
/// <param name="workbook">The workbook</param>
3636
/// <returns>The highlighted workbook</returns>
37-
public Task<XLWorkbook> ValidateAndAnnotateWorkbookAsync(ExcelArguments arguments, XLWorkbook workbook);
37+
public Task<XLWorkbook> ValidateAndAnnotateWorkbookAsync(CultureInfo cultureInfo, XLWorkbook workbook);
3838
}

0 commit comments

Comments
 (0)