6 Commits 1cbe2aa0e3 ... 5ad168d310

Author SHA1 Message Date
  Piotr Czajkowski 5ad168d310 More order 2 months ago
  Piotr Czajkowski b0729f13f5 Order 2 months ago
  Piotr Czajkowski 9ff7824252 Added WriteAll 2 months ago
  Piotr Czajkowski 3fa518940e Added ExcelDynamicWriter 2 months ago
  Piotr Czajkowski 44cc1a0413 Added ReadDifficult 2 months ago
  Piotr Czajkowski 64ffc60617 We need constant number of columns, even if empty 2 months ago

+ 32 - 28
ExcelORM/ExcelORM/ExcelDynamicReader.cs

@@ -24,7 +24,11 @@ public class ExcelDynamicReader
             foreach (var item in mapping)
             {
                 var cell = row.Cell(item.Position);
-                if (cell == null || cell.Value.IsBlank) continue;
+                if (cell == null || cell.Value.IsBlank)
+                {
+                    dynamicRow.Add(item);
+                    continue;
+                }
 
                 if (item.Type == null) item.Type = cell.Value.ValueType();
 
@@ -40,6 +44,33 @@ public class ExcelDynamicReader
         }
     }
 
+    private IEnumerable<List<DynamicCell>> Read(IXLWorksheet? worksheet, uint startFrom = 1, uint skip = 0)
+    {
+        if (worksheet == null) yield break;
+
+        var firstRow = worksheet.Row((int)startFrom);
+        if (firstRow.IsEmpty())
+            firstRow = worksheet.RowsUsed().First(x => x.RowNumber() > startFrom && !x.IsEmpty());
+
+        var mapping = DynamicCell.MapHeader(firstRow.CellsUsed());
+        if (mapping == null || mapping.Count == 0) yield break;
+
+        var rowsToProcess = (ObeyFilter && worksheet.AutoFilter.IsEnabled) switch
+        {
+            true => worksheet.AutoFilter.VisibleRows
+                .Where(x => x.RowNumber() > firstRow.RowNumber())
+                .Select(x => x.WorksheetRow()),
+            false => worksheet.RowsUsed().Where(x => x.RowNumber() > firstRow.RowNumber())
+
+        };
+
+        rowsToProcess = rowsToProcess
+            .Skip((int)skip);
+
+        foreach (var item in ProcessRows(rowsToProcess, mapping))
+            yield return item;
+    }
+
     public IEnumerable<List<DynamicCell>> Read(string? worksheetName, uint startFrom = 1, uint skip = 0)
     {
         var worksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Name.Equals(worksheetName, StringComparison.InvariantCultureIgnoreCase));
@@ -72,31 +103,4 @@ public class ExcelDynamicReader
             };
         }
     }
-
-    private IEnumerable<List<DynamicCell>> Read(IXLWorksheet? worksheet, uint startFrom = 1, uint skip = 0)
-    {
-        if (worksheet == null) yield break;
-
-        var firstRow = worksheet.Row((int)startFrom);
-        if (firstRow.IsEmpty())
-            firstRow = worksheet.RowsUsed().First(x => x.RowNumber() > startFrom && !x.IsEmpty());
-        
-        var mapping = DynamicCell.MapHeader(firstRow.CellsUsed());
-        if (mapping == null || mapping.Count == 0) yield break;
-
-        var rowsToProcess = (ObeyFilter && worksheet.AutoFilter.IsEnabled) switch
-        {
-            true => worksheet.AutoFilter.VisibleRows
-                .Where(x => x.RowNumber() > firstRow.RowNumber())
-                .Select(x => x.WorksheetRow()),
-            false => worksheet.RowsUsed().Where(x => x.RowNumber() > firstRow.RowNumber())
-                
-        };
-
-        rowsToProcess = rowsToProcess
-            .Skip((int)skip);
-        
-        foreach (var item in ProcessRows(rowsToProcess, mapping))
-            yield return item;
-    } 
 }

+ 70 - 0
ExcelORM/ExcelORM/ExcelDynamicWriter.cs

@@ -0,0 +1,70 @@
+using ClosedXML.Excel;
+using ExcelORM.Models;
+
+namespace ExcelORM;
+
+public class ExcelDynamicWriter
+{
+    private readonly IXLWorkbook xlWorkbook;
+    public ExcelDynamicWriter(string? path = null)
+    {
+        xlWorkbook = File.Exists(path) ? new XLWorkbook(path) : new XLWorkbook();
+    }
+
+    private static int GenerateHeader(IXLWorksheet worksheet, IEnumerable<DynamicCell> firstRow)
+    {
+        var rowIndex = 1;
+        foreach (var item in firstRow)
+            worksheet.Cell(rowIndex, item.Position).Value = item.Header;
+
+        return ++rowIndex;
+    }
+
+    private static void Write(IEnumerable<List<DynamicCell>> values, IXLWorksheet worksheet, bool append)
+    {
+        var rowIndex = append switch
+        {
+            true => worksheet.LastRowUsed().RowNumber() + 1,
+            false => GenerateHeader(worksheet, values.First()),
+        };
+
+        foreach (var row in values)
+        {
+            foreach (var cell in row)
+            {
+                if (cell.Value == null) continue;
+
+                worksheet.Cell(rowIndex, cell.Position).Value = XLCellValue.FromObject(cell.Value);
+            }
+
+            rowIndex++;
+        }
+    }
+
+    public void Write(IEnumerable<List<DynamicCell>>? values, string? worksheetName = null, bool append = false)
+    {
+        if (values == null) return;
+
+        var xlWorksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Name.Equals(worksheetName, StringComparison.InvariantCultureIgnoreCase));
+        
+        xlWorksheet ??= !string.IsNullOrWhiteSpace(worksheetName) ?
+            xlWorkbook.AddWorksheet(worksheetName)
+            : xlWorkbook.Worksheets.Count == 0 ? xlWorkbook.AddWorksheet() : xlWorkbook.Worksheets.First();
+
+        Write(values, xlWorksheet, append);
+    }
+
+    public void WriteAll(IEnumerable<DynamicWorksheet>? dynamicWorksheets, bool append = false)
+    {
+        if (dynamicWorksheets == null) return;
+
+        foreach (var dynamicWorksheet in dynamicWorksheets)
+            Write(dynamicWorksheet.Cells, dynamicWorksheet.Name, append);
+    }
+
+    public void SaveAs(string path, IExcelConverter? converter = null)
+    {
+        xlWorkbook.SaveAs(path);
+        converter?.MakeCompatible(path);
+    } 
+}

+ 27 - 27
ExcelORM/ExcelORM/ExcelReader.cs

@@ -39,6 +39,33 @@ public class ExcelReader
         }
     }
 
+    private IEnumerable<T> Read<T>(IXLWorksheet? worksheet, uint startFrom, uint skip) where T : class, new()
+    {
+        if (worksheet == null) yield break;
+
+        var firstRow = worksheet.Row((int)startFrom);
+        if (firstRow.IsEmpty())
+            firstRow = worksheet.RowsUsed().First(x => x.RowNumber() > startFrom && !x.IsEmpty());
+
+        var mapping = Mapping.MapProperties<T>(firstRow.CellsUsed());
+        if (mapping == null) yield break;
+
+        var rowsToProcess = (ObeyFilter && worksheet.AutoFilter.IsEnabled) switch
+        {
+            true => worksheet.AutoFilter.VisibleRows
+                .Where(x => x.RowNumber() > firstRow.RowNumber())
+                .Select(x => x.WorksheetRow()),
+            false => worksheet.RowsUsed().Where(x => x.RowNumber() > firstRow.RowNumber())
+
+        };
+
+        rowsToProcess = rowsToProcess
+            .Skip((int)skip);
+
+        foreach (var item in ProcessRows<T>(rowsToProcess, mapping))
+            yield return item;
+    }
+
     public IEnumerable<T> Read<T>(string? worksheetName, uint startFrom = 1, uint skip = 0) where T : class, new()
     {
         var worksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Name.Equals(worksheetName, StringComparison.InvariantCultureIgnoreCase));
@@ -67,31 +94,4 @@ public class ExcelReader
                 yield return item;
         }
     }
-
-    private IEnumerable<T> Read<T>(IXLWorksheet? worksheet, uint startFrom, uint skip) where T : class, new()
-    {
-        if (worksheet == null) yield break;
-
-        var firstRow = worksheet.Row((int)startFrom);
-        if (firstRow.IsEmpty())
-            firstRow = worksheet.RowsUsed().First(x => x.RowNumber() > startFrom && !x.IsEmpty());
-        
-        var mapping = Mapping.MapProperties<T>(firstRow.CellsUsed());
-        if (mapping == null) yield break;
-
-        var rowsToProcess = (ObeyFilter && worksheet.AutoFilter.IsEnabled) switch
-        {
-            true => worksheet.AutoFilter.VisibleRows
-                .Where(x => x.RowNumber() > firstRow.RowNumber())
-                .Select(x => x.WorksheetRow()),
-            false => worksheet.RowsUsed().Where(x => x.RowNumber() > firstRow.RowNumber())
-                
-        };
-
-        rowsToProcess = rowsToProcess
-            .Skip((int)skip);
-        
-        foreach (var item in ProcessRows<T>(rowsToProcess, mapping))
-            yield return item;
-    } 
 }

+ 12 - 12
ExcelORM/ExcelORM/ExcelWriter.cs

@@ -26,17 +26,6 @@ public class ExcelWriter
         return ++rowIndex;
     }
 
-    public void Write<T>(IEnumerable<T> values, string? worksheetName = null, bool append = false) where T : class, new()
-    {
-        var xlWorksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Name.Equals(worksheetName, StringComparison.InvariantCultureIgnoreCase));
-        
-        xlWorksheet ??= !string.IsNullOrWhiteSpace(worksheetName) ?
-            xlWorkbook.AddWorksheet(worksheetName)
-            : xlWorkbook.Worksheets.Count == 0 ? xlWorkbook.AddWorksheet() : xlWorkbook.Worksheets.First();
-
-        Write(values, xlWorksheet, append);
-    }
-
     private static void Write<T>(IEnumerable<T> values, IXLWorksheet worksheet, bool append) where T : class, new()
     {
         var enumerable = values as T[] ?? values.ToArray();
@@ -56,7 +45,7 @@ public class ExcelWriter
             {
                 var valueToSet = property.GetValue(value);
                 if (valueToSet == null) continue;
-                
+
                 worksheet.Cell(rowIndex, cellIndex).Value = XLCellValue.FromObject(valueToSet);
                 cellIndex++;
             }
@@ -65,6 +54,17 @@ public class ExcelWriter
         }
     }
 
+    public void Write<T>(IEnumerable<T> values, string? worksheetName = null, bool append = false) where T : class, new()
+    {
+        var xlWorksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Name.Equals(worksheetName, StringComparison.InvariantCultureIgnoreCase));
+        
+        xlWorksheet ??= !string.IsNullOrWhiteSpace(worksheetName) ?
+            xlWorkbook.AddWorksheet(worksheetName)
+            : xlWorkbook.Worksheets.Count == 0 ? xlWorkbook.AddWorksheet() : xlWorkbook.Worksheets.First();
+
+        Write(values, xlWorksheet, append);
+    }
+
     public void SaveAs(string path, IExcelConverter? converter = null)
     {
         xlWorkbook.SaveAs(path);

+ 10 - 0
ExcelORM/ExcelORMTests/DynamicReaderTests.cs

@@ -7,6 +7,7 @@ public class DynamicReaderTests
     private const string RegularFile = "testFiles/first.xlsx";
     private const string DifferentTypesFile = "testFiles/differentTypes.xlsx";
     private const string MultipleSheetsFile = "testFiles/multipleSheets.xlsx";
+    private const string DifficultFile = "testFiles/dynamicDifficult.xlsx";
 
     [Fact]
     public void Read()
@@ -38,4 +39,13 @@ public class DynamicReaderTests
         var results = reader.ReadAll().ToArray();
         Assert.NotEmpty(results);
     }
+
+    [Fact]
+    public void ReadDifficult()
+    {
+        var reader = new ExcelDynamicReader(DifficultFile);
+        var results = reader.Read().ToArray();
+        Assert.NotEmpty(results);
+        Assert.Equal(results.First().Count, results.Last().Count);
+    }
 }

+ 57 - 0
ExcelORM/ExcelORMTests/DynamicWriterTests.cs

@@ -0,0 +1,57 @@
+using ExcelORM;
+
+namespace ExcelORMTests;
+
+public class DynamicWriterTests
+{
+    private const string DifficultFile = "testFiles/dynamicDifficult.xlsx";
+    private const string MultipleSheetsFile = "testFiles/multipleSheets.xlsx";
+
+    [Fact]
+    public void Write()
+    {
+        var testFile = Path.GetRandomFileName();
+        testFile = Path.ChangeExtension(testFile, "xlsx");
+
+        var reader = new ExcelDynamicReader(DifficultFile);
+        var results = reader.Read().ToArray();
+        Assert.NotEmpty(results);
+
+        var writer = new ExcelDynamicWriter();
+        writer.Write(results);
+        writer.SaveAs(testFile);
+
+        var savedReader = new ExcelDynamicReader(testFile);
+        var savedResults = savedReader.Read().ToArray();
+        Assert.NotEmpty(savedResults);
+        Assert.True(results.First().SequenceEqual(savedResults.First()));
+        Assert.True(results.Last().SequenceEqual(savedResults.Last()));
+
+        File.Delete(testFile);
+    }
+
+    [Fact]
+    public void WriteAll()
+    {
+        var testFile = Path.GetRandomFileName();
+        testFile = Path.ChangeExtension(testFile, "xlsx");
+
+        var reader = new ExcelDynamicReader(MultipleSheetsFile);
+        var results = reader.ReadAll().ToArray();
+        Assert.NotEmpty(results);
+        
+        var writer = new ExcelDynamicWriter();
+        writer.WriteAll(results);
+        writer.SaveAs(testFile);
+
+        var savedReader = new ExcelDynamicReader(testFile);
+        var savedResults = savedReader.ReadAll().ToArray();
+        Assert.NotEmpty(savedResults);
+        Assert.Equal(results.First().Name, savedResults.First().Name);
+        Assert.Equal(results.First().Cells?.Count(), savedResults.First().Cells?.Count());
+        Assert.Equal(results.Last().Name, savedResults.Last().Name);
+        Assert.Equal(results.Last().Cells?.Count(), savedResults.Last().Cells?.Count());
+
+        File.Delete(testFile);
+    }
+}

+ 5 - 2
ExcelORM/ExcelORMTests/ExcelORMTests.csproj

@@ -10,8 +10,8 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
-        <PackageReference Include="xunit" Version="2.4.2"/>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
+        <PackageReference Include="xunit" Version="2.4.2" />
         <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
             <PrivateAssets>all</PrivateAssets>
@@ -27,6 +27,9 @@
     </ItemGroup>
 
     <ItemGroup>
+      <None Update="testFiles\dynamicDifficult.xlsx">
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </None>
       <None Update="testFiles\hidden.xlsx">
         <CopyToOutputDirectory>Always</CopyToOutputDirectory>
       </None>

BIN
ExcelORM/ExcelORMTests/testFiles/dynamicDifficult.xlsx