12 Commits 87536f1fc4 ... b9bfe16d5f

Autore SHA1 Messaggio Data
  Piotr Czajkowski b9bfe16d5f Information about new version 3 settimane fa
  Piotr Czajkowski 75eb508959 Cleanup 3 settimane fa
  Piotr Czajkowski 0eade4cd86 Cosmetics 3 settimane fa
  Piotr Czajkowski d49c825ab1 Fixed small issues 3 settimane fa
  Piotr Czajkowski 2b9519040b Fixed empty file reading 3 settimane fa
  Piotr Czajkowski ec984f919e If empty file let's return empty 3 settimane fa
  Piotr Czajkowski d5ef95c8e3 Fixed append when empty file 3 settimane fa
  Piotr Czajkowski 33bf9273c3 Added WriteWithAppend 3 settimane fa
  Piotr Czajkowski 8cb334bbfd Append now works with empty file 3 settimane fa
  Piotr Czajkowski b6da0a6972 Testing more types 3 settimane fa
  Piotr Czajkowski 698499ea68 Should work better 3 settimane fa
  Piotr Czajkowski 64bb722207 Handling more number types 3 settimane fa

+ 3 - 1
ExcelORM/ExcelORM/ExcelDynamicReader.cs

@@ -54,7 +54,9 @@ public class ExcelDynamicReader : IDisposable
 
         var firstRow = worksheet.Row((int)startFrom);
         if (firstRow.IsEmpty())
-            firstRow = worksheet.RowsUsed().First(x => x.RowNumber() > startFrom && !x.IsEmpty());
+            firstRow = worksheet.RowsUsed().FirstOrDefault(x => x.RowNumber() > startFrom && !x.IsEmpty());
+        
+        if (firstRow == null || firstRow.IsEmpty()) yield break;
 
         var mapping = DynamicCell.MapHeader(firstRow.CellsUsed());
         if (mapping == null || mapping.Count == 0) yield break;

+ 5 - 4
ExcelORM/ExcelORM/ExcelDynamicWriter.cs

@@ -28,21 +28,22 @@ public class ExcelDynamicWriter : IDisposable
 
     private static void Write(IEnumerable<List<DynamicCell>> values, IXLWorksheet worksheet, bool append)
     {
+        var lastRow = worksheet.LastRowUsed();
+        if (lastRow == null) append = false;
+        
         var rowIndex = append switch
         {
-            true => worksheet.LastRowUsed()?.RowNumber() + 1,
+            true => lastRow.RowNumber() + 1,
             false => GenerateHeader(worksheet, values.First()),
         };
 
-        if (rowIndex == null) throw new NullReferenceException(nameof(rowIndex));
-
         foreach (var row in values)
         {
             foreach (var cell in row)
             {
                 if (cell.Value == null) continue;
 
-                worksheet.Cell(rowIndex.Value, cell.Position).Value = XLCellValue.FromObject(cell.Value);
+                worksheet.Cell(rowIndex, cell.Position).Value = XLCellValue.FromObject(cell.Value);
             }
 
             rowIndex++;

+ 3 - 3
ExcelORM/ExcelORM/ExcelORM.csproj

@@ -4,7 +4,7 @@
         <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
-        <Version>2.7.0</Version>
+        <Version>2.8.0</Version>
         <PackageProjectUrl>https://git.liox.eu/pczajkowski/ExcelORM</PackageProjectUrl>
         <RepositoryUrl>https://github.com/pczajkowski/ExcelORM</RepositoryUrl>
         <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
@@ -14,11 +14,11 @@
 	<Authors>Piotr Czajkowski</Authors>
 	<Description>Simple library to read/write C# objects from/to Excel files. </Description>
 	<RepositoryType>GitHub</RepositoryType>
-	<PackageReleaseNotes>Able to read enums and Guids.</PackageReleaseNotes>
+	<PackageReleaseNotes>Handling more number types. Properly handling appending to and reading from empty file.</PackageReleaseNotes>
     </PropertyGroup>
 
     <ItemGroup>
-	    <PackageReference Include="ClosedXML" Version="0.104.2" />
+	    <PackageReference Include="ClosedXML" Version="0.105.0" />
 	    <None Include="../../README.md" Pack="true" PackagePath="\" />
 	    <None Include="../../LICENSE" Pack="true" PackagePath="" />
     </ItemGroup>

+ 3 - 1
ExcelORM/ExcelORM/ExcelReader.cs

@@ -67,7 +67,9 @@ public class ExcelReader : IDisposable
 
         var firstRow = worksheet.Row((int)startFrom);
         if (firstRow.IsEmpty())
-            firstRow = worksheet.RowsUsed().First(x => x.RowNumber() > startFrom && !x.IsEmpty());
+            firstRow = worksheet.RowsUsed().FirstOrDefault(x => x.RowNumber() > startFrom && !x.IsEmpty());
+        
+        if (firstRow == null || firstRow.IsEmpty()) yield break;
 
         var mapping = Mapping.MapProperties<T>(firstRow.CellsUsed());
         if (mapping == null) yield break;

+ 6 - 5
ExcelORM/ExcelORM/ExcelWriter.cs

@@ -82,10 +82,13 @@ public class ExcelWriter : IDisposable
         var properties = typeof(T).GetProperties();
         List<Mapping>? mapping = [];
 
+        var lastRow = worksheet.LastRowUsed();
+        if (lastRow == null) append = false;
+        
         var rowIndex = (append, startFrom: appendFrom) switch
         { 
             (true, not null) => (int)appendFrom,
-            (true, null) => worksheet.LastRowUsed()?.RowNumber() + 1,
+            (true, null) => lastRow.RowNumber() + 1,
             _ => GenerateHeader(worksheet, properties) 
         };
 
@@ -96,12 +99,10 @@ public class ExcelWriter : IDisposable
             if (mapping == null || mapping.Count == 0) return;
         }
 
-        if (rowIndex == null) throw new NullReferenceException(nameof(rowIndex));
-
         foreach (var value in values)
         {
-            if (append) WriteRowAppend(value, worksheet, properties, rowIndex.Value, mapping);
-            else WriteRow(value, worksheet, properties, rowIndex.Value);
+            if (append) WriteRowAppend(value, worksheet, properties, rowIndex, mapping);
+            else WriteRow(value, worksheet, properties, rowIndex);
             
             rowIndex++;
         }

+ 25 - 1
ExcelORM/ExcelORM/TypeExtensions.cs

@@ -1,3 +1,4 @@
+using System.Globalization;
 using System.Reflection;
 using ClosedXML.Excel;
 using ExcelORM.Attributes;
@@ -34,6 +35,29 @@ public static class TypeExtensions
         
         return value.GetText(); 
     }
+
+    private static object? GetSpecificNumberType(XLCellValue value, PropertyInfo? property)
+    {
+        var rawNumber = value.GetNumber();
+        if (property == null) return rawNumber;
+
+        var targetType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
+        if (!targetType.IsPrimitive && targetType != typeof(decimal)) return rawNumber;
+
+        try
+        {
+            return Convert.ChangeType(rawNumber, targetType, CultureInfo.InvariantCulture);
+        }
+        catch (InvalidCastException)
+        {
+            return rawNumber;
+        }
+        catch (OverflowException)
+        {
+            if (Nullable.GetUnderlyingType(property.PropertyType) != null) return null;
+            throw;
+        }
+    }
     
     // Borrowed from https://github.com/ClosedXML/ClosedXML/blob/develop/ClosedXML/Excel/XLCellValue.cs#L361
     public static object? ToObject(this XLCellValue value, PropertyInfo? property = null)
@@ -42,7 +66,7 @@ public static class TypeExtensions
         {
             XLDataType.Blank => null,
             XLDataType.Boolean => value.GetBoolean(),
-            XLDataType.Number => value.GetNumber(),
+            XLDataType.Number => GetSpecificNumberType(value, property),
             XLDataType.Text => GetAdditionalTypeFromText(value, property),
             XLDataType.Error => value.GetError(),
             XLDataType.DateTime => value.GetDateTime(),

+ 10 - 0
ExcelORM/ExcelORMTests/DynamicReaderTests.cs

@@ -48,4 +48,14 @@ public class DynamicReaderTests
         Assert.NotEmpty(results);
         Assert.Equal(results.First().Count, results.Last().Count);
     }
+    
+    private const string EmptyFileForAppend = "testFiles/emptyForAppend.xlsx";
+    
+    [Fact]
+    public void ReadEmptyFile()
+    {
+        using var reader = new ExcelDynamicReader(EmptyFileForAppend);
+        var results = reader.Read().ToArray();
+        Assert.Empty(results);
+    } 
 }

+ 54 - 0
ExcelORM/ExcelORMTests/DynamicWriterTests.cs

@@ -30,6 +30,60 @@ public class DynamicWriterTests
 
         File.Delete(testFile);
     }
+    
+    [Fact]
+    public void WriteWithAppend()
+    {
+        var testFile = Path.GetRandomFileName();
+        testFile = Path.ChangeExtension(testFile, "xlsx");
+
+        using var reader = new ExcelDynamicReader(DifficultFile);
+        var results = reader.Read().ToArray();
+        Assert.NotEmpty(results);
+
+        using var writer = new ExcelDynamicWriter();
+        writer.Write(results);
+        writer.SaveAs(testFile);
+        
+        using var writerAppend = new ExcelDynamicWriter(testFile);
+        writerAppend.Write(results, append: true);
+        writerAppend.SaveAs(testFile);
+
+        using 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()));
+        Assert.Equal(results.Length * 2, savedResults.Length);
+
+        File.Delete(testFile);
+    }
+    
+    private const string EmptyFileForAppend = "testFiles/emptyForAppend.xlsx";
+    
+    [Fact]
+    public void WriteWithAppendEmptyFile()
+    {
+        var testFile = Path.GetRandomFileName();
+        testFile = Path.ChangeExtension(testFile, "xlsx");
+        File.Copy(EmptyFileForAppend, testFile);
+
+        using var reader = new ExcelDynamicReader(DifficultFile);
+        var results = reader.Read().ToArray();
+        Assert.NotEmpty(results);
+
+        using var writerAppend = new ExcelDynamicWriter(testFile);
+        writerAppend.Write(results, append: true);
+        writerAppend.SaveAs(testFile);
+
+        using 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()

+ 5 - 2
ExcelORM/ExcelORMTests/ExcelORMTests.csproj

@@ -10,9 +10,9 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
         <PackageReference Include="xunit" Version="2.9.3" />
-        <PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
+        <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
             <PrivateAssets>all</PrivateAssets>
         </PackageReference>
@@ -62,6 +62,9 @@
       <None Update="testFiles\additionalTypes.xlsx">
         <CopyToOutputDirectory>Always</CopyToOutputDirectory>
       </None>
+      <None Update="testFiles\emptyForAppend.xlsx">
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </None>
     </ItemGroup>
 
     <ItemGroup>

+ 10 - 0
ExcelORM/ExcelORMTests/ReaderTests.cs

@@ -135,4 +135,14 @@ public class ReaderTests
         var results = reader.Read<TestAdditionalTypesNullable>().ToArray();
         Assert.NotEmpty(results);
     }
+    
+    private const string EmptyFileForAppend = "testFiles/emptyForAppend.xlsx";
+    
+    [Fact]
+    public void ReadEmptyFile()
+    {
+        using var reader = new ExcelReader(EmptyFileForAppend);
+        var results = reader.Read<Test>().ToArray();
+        Assert.Empty(results);
+    }
 }

+ 6 - 1
ExcelORM/ExcelORMTests/TestTypes.cs

@@ -5,6 +5,11 @@ public record TestTypes
     public string? Text { get; set; }
     public DateTime? Date { get; set; }
     public TimeSpan? TimeSpan { get; set; }
-    public double? Int { get; set; }
+    public int? Int { get; set; }
     public double? Double { get; set; }
+    public decimal? Decimal { get; set; }
+    public long? Long { get; set; }
+    public short? Short { get; set; }
+    public float? Float { get; set; }
+    public byte? Byte { get; set; }
 }

+ 22 - 0
ExcelORM/ExcelORMTests/WriterTests.cs

@@ -136,6 +136,28 @@ public class WriterTests
 
         File.Delete(testFile);
     }
+    
+    private const string EmptyFileForAppend = "testFiles/emptyForAppend.xlsx";
+    [Fact]
+    public void WriteWithAppendEmpty()
+    {
+        var testFile = Path.GetRandomFileName();
+        testFile = Path.ChangeExtension(testFile, "xlsx");
+        File.Copy(EmptyFileForAppend, testFile);
+
+        using var writer = new ExcelWriter(testFile);
+        writer.Write(ArrayOfThree, append: true);
+        writer.SaveAs(testFile);
+
+        using var reader = new ExcelReader(testFile);
+        var readArray = reader.Read<Test>().ToArray();
+        Assert.Equal(3, readArray.Length);
+
+        for (int i = 0; i < ArrayOfThree.Length; i++)
+            Assert.Equal(ArrayOfThree[i], readArray[i]);
+        
+        File.Delete(testFile);
+    }
 
     [Fact]
     public void WriteDifferentTypes()

BIN
ExcelORM/ExcelORMTests/testFiles/differentTypes.xlsx


BIN
ExcelORM/ExcelORMTests/testFiles/emptyForAppend.xlsx


+ 2 - 0
README.md

@@ -15,6 +15,8 @@ It currently supports properties of types as supported by ClosedXML, so:
 - DateTime
 - TimeSpan
 
+From version 2.8.0 it supports additional numeric types: decimal, float, long, int, short, byte and their nullable variants.
+
 Additionally, it supports:
 - Guid (if not a valid Guid and non-nullable then empty Guid will be returned)
 - enum (if not a valid enum value and non-nullable then default value will be returned)

+ 1 - 0
versions.md

@@ -8,3 +8,4 @@
 | 2.5.0 | Added Location to ArgumentException Message in ExcelReader. It'll show address of affected cell and the worksheet's name.|
 | 2.6.0 | Added support for appending starting from given row.|
 | 2.7.0 | Added support for reading properties of type Guid and enum.|
+| 2.8.0 | Handling more number types. Properly handling appending to and reading from empty file.