From 9e420f64551183a47a2fd3993957fd33fcde42a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Tue, 17 Dec 2024 11:07:46 +0100 Subject: [PATCH 01/12] Modernize projects * Target .NET 8 instead of .NET Core 3.1 for sample and tests * Update test dependencies to their latest versions --- src/Simplexcel.TestApp/Simplexcel.TestApp.csproj | 2 +- src/Simplexcel.Tests/Simplexcel.Tests.csproj | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Simplexcel.TestApp/Simplexcel.TestApp.csproj b/src/Simplexcel.TestApp/Simplexcel.TestApp.csproj index 404e020..66a2a76 100644 --- a/src/Simplexcel.TestApp/Simplexcel.TestApp.csproj +++ b/src/Simplexcel.TestApp/Simplexcel.TestApp.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net8.0 false diff --git a/src/Simplexcel.Tests/Simplexcel.Tests.csproj b/src/Simplexcel.Tests/Simplexcel.Tests.csproj index 7ec0531..9bd0cac 100644 --- a/src/Simplexcel.Tests/Simplexcel.Tests.csproj +++ b/src/Simplexcel.Tests/Simplexcel.Tests.csproj @@ -1,25 +1,20 @@  - netcoreapp3.1 - false + net8.0 true ../simplexcel_oss.snk false - - - + + + - - - - From d57a7f428fc505bcde0bbc72e387b52001e8717a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Tue, 17 Dec 2024 13:26:21 +0100 Subject: [PATCH 02/12] Migrate Simplexcel.MvcTestApp to ASP.NET Core --- .../{ => Controllers}/HomeController.cs | 4 +- src/Simplexcel.MvcTestApp/ExcelResultBase.cs | 21 ++- .../ExcelTestActionResult.cs | 4 +- src/Simplexcel.MvcTestApp/Global.asax | 1 - src/Simplexcel.MvcTestApp/Global.asax.cs | 19 --- src/Simplexcel.MvcTestApp/Program.cs | 12 ++ .../Properties/AssemblyInfo.cs | 35 ---- .../Properties/launchSettings.json | 38 +++++ .../Simplexcel.MvcTestApp.csproj | 161 +----------------- .../Views/Home/Index.cshtml | 2 +- src/Simplexcel.MvcTestApp/Views/Web.config | 42 ----- src/Simplexcel.MvcTestApp/Web.config | 50 ------ src/Simplexcel.MvcTestApp/packages.config | 7 - .../{ => wwwroot}/favicon.ico | Bin 14 files changed, 76 insertions(+), 320 deletions(-) rename src/Simplexcel.MvcTestApp/{ => Controllers}/HomeController.cs (92%) delete mode 100644 src/Simplexcel.MvcTestApp/Global.asax delete mode 100644 src/Simplexcel.MvcTestApp/Global.asax.cs create mode 100644 src/Simplexcel.MvcTestApp/Program.cs delete mode 100644 src/Simplexcel.MvcTestApp/Properties/AssemblyInfo.cs create mode 100644 src/Simplexcel.MvcTestApp/Properties/launchSettings.json delete mode 100644 src/Simplexcel.MvcTestApp/Views/Web.config delete mode 100644 src/Simplexcel.MvcTestApp/Web.config delete mode 100644 src/Simplexcel.MvcTestApp/packages.config rename src/Simplexcel.MvcTestApp/{ => wwwroot}/favicon.ico (100%) diff --git a/src/Simplexcel.MvcTestApp/HomeController.cs b/src/Simplexcel.MvcTestApp/Controllers/HomeController.cs similarity index 92% rename from src/Simplexcel.MvcTestApp/HomeController.cs rename to src/Simplexcel.MvcTestApp/Controllers/HomeController.cs index dc28b3f..68d1c1f 100644 --- a/src/Simplexcel.MvcTestApp/HomeController.cs +++ b/src/Simplexcel.MvcTestApp/Controllers/HomeController.cs @@ -1,6 +1,6 @@ -using System; +using System; using System.Collections.Generic; -using System.Web.Mvc; +using Microsoft.AspNetCore.Mvc; namespace Simplexcel.MvcTestApp.Controllers { diff --git a/src/Simplexcel.MvcTestApp/ExcelResultBase.cs b/src/Simplexcel.MvcTestApp/ExcelResultBase.cs index 2dcf8fd..048d646 100644 --- a/src/Simplexcel.MvcTestApp/ExcelResultBase.cs +++ b/src/Simplexcel.MvcTestApp/ExcelResultBase.cs @@ -1,7 +1,10 @@ using System; using System.IO; -using System.Net; -using System.Web.Mvc; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; +using static Microsoft.AspNetCore.Http.StatusCodes; namespace Simplexcel.MvcTestApp { @@ -14,9 +17,9 @@ protected ExcelResultBase(string filename) _filename = filename; } - protected abstract Workbook GenerateWorkbook(); + protected abstract Workbook? GenerateWorkbook(); - public override void ExecuteResult(ControllerContext context) + public override async Task ExecuteResultAsync(ActionContext context) { if (context == null) { @@ -26,15 +29,17 @@ public override void ExecuteResult(ControllerContext context) var workbook = GenerateWorkbook(); if (workbook == null) { - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + context.HttpContext.Response.StatusCode = Status404NotFound; return; } context.HttpContext.Response.ContentType = "application/octet-stream"; - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; - context.HttpContext.Response.AppendHeader("content-disposition", "attachment; filename=\"" + _filename + "\""); + context.HttpContext.Response.StatusCode = Status200OK; + context.HttpContext.Response.GetTypedHeaders().ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = _filename }; - workbook.Save(context.HttpContext.Response.OutputStream); + using var ms = new MemoryStream(); + workbook.Save(ms); + await ms.CopyToAsync(context.HttpContext.Response.Body, context.HttpContext.RequestAborted); } } } \ No newline at end of file diff --git a/src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs b/src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs index 8c9eef6..0429c43 100644 --- a/src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs +++ b/src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs @@ -5,9 +5,9 @@ namespace Simplexcel.MvcTestApp { public class ExcelTestActionResult : ExcelResultBase { - private readonly List _data; + private readonly List? _data; - public ExcelTestActionResult(string filename, List data) : base(filename) + public ExcelTestActionResult(string filename, List? data) : base(filename) { _data = data; } diff --git a/src/Simplexcel.MvcTestApp/Global.asax b/src/Simplexcel.MvcTestApp/Global.asax deleted file mode 100644 index 42a8631..0000000 --- a/src/Simplexcel.MvcTestApp/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="Simplexcel.MvcTestApp.MvcApplication" Language="C#" %> diff --git a/src/Simplexcel.MvcTestApp/Global.asax.cs b/src/Simplexcel.MvcTestApp/Global.asax.cs deleted file mode 100644 index dd52108..0000000 --- a/src/Simplexcel.MvcTestApp/Global.asax.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Web.Mvc; -using System.Web.Routing; - -namespace Simplexcel.MvcTestApp -{ - public class MvcApplication : System.Web.HttpApplication - { - protected void Application_Start() - { - RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); - - RouteTable.Routes.MapRoute( - name: "Default", - url: "{controller}/{action}/{id}", - defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } - ); - } - } -} diff --git a/src/Simplexcel.MvcTestApp/Program.cs b/src/Simplexcel.MvcTestApp/Program.cs new file mode 100644 index 0000000..0bcd9e3 --- /dev/null +++ b/src/Simplexcel.MvcTestApp/Program.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllersWithViews(); + +var app = builder.Build(); + +app.UseStaticFiles(); +app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); + +app.Run(); diff --git a/src/Simplexcel.MvcTestApp/Properties/AssemblyInfo.cs b/src/Simplexcel.MvcTestApp/Properties/AssemblyInfo.cs deleted file mode 100644 index 2b95147..0000000 --- a/src/Simplexcel.MvcTestApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Simplexcel.MvcTestApp")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Simplexcel.MvcTestApp")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("02275a57-537d-47a0-bf4a-d2e2d5c3700c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Simplexcel.MvcTestApp/Properties/launchSettings.json b/src/Simplexcel.MvcTestApp/Properties/launchSettings.json new file mode 100644 index 0000000..f38ad5b --- /dev/null +++ b/src/Simplexcel.MvcTestApp/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:11053", + "sslPort": 44390 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5208", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7298;http://localhost:5208", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj b/src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj index 5a722f6..9c25065 100644 --- a/src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj +++ b/src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj @@ -1,157 +1,12 @@ - - - + + - Debug - AnyCPU - - - 2.0 - {26E384B1-4991-4B7B-B68C-4DDB500AE37C} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - Simplexcel.MvcTestApp - Simplexcel.MvcTestApp - v4.7 - false - true - - - - - - - + net8.0 + enable - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - true - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - True - ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - - - - - True - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll - - - True - ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll - - - True - ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll - - - True - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll - - - True - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll - - - True - ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll - - - - - - - - Global.asax - - - + - - - - - + - - - {39afae0e-34d0-43ad-acd7-39fbe03a57f5} - Simplexcel - - - - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - - - - True - True - 59984 - / - http://localhost:59984/ - False - False - - - False - - - - - - \ No newline at end of file + + diff --git a/src/Simplexcel.MvcTestApp/Views/Home/Index.cshtml b/src/Simplexcel.MvcTestApp/Views/Home/Index.cshtml index 9cd960f..86a2dad 100644 --- a/src/Simplexcel.MvcTestApp/Views/Home/Index.cshtml +++ b/src/Simplexcel.MvcTestApp/Views/Home/Index.cshtml @@ -1,6 +1,6 @@  - Simplexcel ASP.net MVC Test App + Simplexcel ASP.NET Core MVC Test App Download Excel Sheet diff --git a/src/Simplexcel.MvcTestApp/Views/Web.config b/src/Simplexcel.MvcTestApp/Views/Web.config deleted file mode 100644 index db9a290..0000000 --- a/src/Simplexcel.MvcTestApp/Views/Web.config +++ /dev/null @@ -1,42 +0,0 @@ - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Simplexcel.MvcTestApp/Web.config b/src/Simplexcel.MvcTestApp/Web.config deleted file mode 100644 index 9279995..0000000 --- a/src/Simplexcel.MvcTestApp/Web.config +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Simplexcel.MvcTestApp/packages.config b/src/Simplexcel.MvcTestApp/packages.config deleted file mode 100644 index b8f630f..0000000 --- a/src/Simplexcel.MvcTestApp/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/Simplexcel.MvcTestApp/favicon.ico b/src/Simplexcel.MvcTestApp/wwwroot/favicon.ico similarity index 100% rename from src/Simplexcel.MvcTestApp/favicon.ico rename to src/Simplexcel.MvcTestApp/wwwroot/favicon.ico From 42907056e9c6ed5be762b563367eb2a2dcae3ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Tue, 17 Dec 2024 13:48:17 +0100 Subject: [PATCH 03/12] Simplify the Simplexcel.MvcTestApp sample --- .../Controllers/HomeController.cs | 29 +++---- src/Simplexcel.MvcTestApp/ExcelResultBase.cs | 45 ----------- .../ExcelTestActionResult.cs | 81 ------------------- src/Simplexcel.MvcTestApp/Sample.cs | 73 +++++++++++++++++ 4 files changed, 84 insertions(+), 144 deletions(-) delete mode 100644 src/Simplexcel.MvcTestApp/ExcelResultBase.cs delete mode 100644 src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs create mode 100644 src/Simplexcel.MvcTestApp/Sample.cs diff --git a/src/Simplexcel.MvcTestApp/Controllers/HomeController.cs b/src/Simplexcel.MvcTestApp/Controllers/HomeController.cs index 68d1c1f..9b41461 100644 --- a/src/Simplexcel.MvcTestApp/Controllers/HomeController.cs +++ b/src/Simplexcel.MvcTestApp/Controllers/HomeController.cs @@ -1,25 +1,18 @@ -using System; -using System.Collections.Generic; +using System.IO; using Microsoft.AspNetCore.Mvc; -namespace Simplexcel.MvcTestApp.Controllers +namespace Simplexcel.MvcTestApp.Controllers; + +public class HomeController : Controller { - public class HomeController : Controller - { - public ActionResult Index() - { - return View(); - } + public ActionResult Index() => View(); - public ActionResult ExcelTest() - { - var businessData = new List(); - for (int i = 1; i < 55; i++) - { - businessData.Add(Guid.NewGuid().ToString()); - } + public ActionResult ExcelTest() + { + var workbook = Sample.GenerateWorkbook(); - return new ExcelTestActionResult("test.xlsx", businessData); - } + var stream = new MemoryStream(); + workbook.Save(stream); + return File(fileStream: stream, contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileDownloadName: "test.xlsx"); } } \ No newline at end of file diff --git a/src/Simplexcel.MvcTestApp/ExcelResultBase.cs b/src/Simplexcel.MvcTestApp/ExcelResultBase.cs deleted file mode 100644 index 048d646..0000000 --- a/src/Simplexcel.MvcTestApp/ExcelResultBase.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Net.Http.Headers; -using static Microsoft.AspNetCore.Http.StatusCodes; - -namespace Simplexcel.MvcTestApp -{ - public abstract class ExcelResultBase : ActionResult - { - private readonly string _filename; - - protected ExcelResultBase(string filename) - { - _filename = filename; - } - - protected abstract Workbook? GenerateWorkbook(); - - public override async Task ExecuteResultAsync(ActionContext context) - { - if (context == null) - { - throw new ArgumentNullException("context"); - } - - var workbook = GenerateWorkbook(); - if (workbook == null) - { - context.HttpContext.Response.StatusCode = Status404NotFound; - return; - } - - context.HttpContext.Response.ContentType = "application/octet-stream"; - context.HttpContext.Response.StatusCode = Status200OK; - context.HttpContext.Response.GetTypedHeaders().ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = _filename }; - - using var ms = new MemoryStream(); - workbook.Save(ms); - await ms.CopyToAsync(context.HttpContext.Response.Body, context.HttpContext.RequestAborted); - } - } -} \ No newline at end of file diff --git a/src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs b/src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs deleted file mode 100644 index 0429c43..0000000 --- a/src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Simplexcel.MvcTestApp -{ - public class ExcelTestActionResult : ExcelResultBase - { - private readonly List? _data; - - public ExcelTestActionResult(string filename, List? data) : base(filename) - { - _data = data; - } - - protected override Workbook GenerateWorkbook() - { - var wb = new Workbook - { - Title = "Workbook Title", - Author = "The Author" - }; - - var sheet = new Worksheet("Test"); - sheet.PageSetup.Orientation = Orientation.Landscape; - sheet.PageSetup.PrintRepeatRows = 2; - sheet.PageSetup.PrintRepeatColumns = 2; - - sheet.ColumnWidths[0] = 24.6; - - sheet.Cells["A1"] = "Test"; - sheet.Cells["A1"].FontName = "Arial Black"; - - sheet.Cells[0, 1] = "Another Test"; - sheet.Cells[0, 1].Border = CellBorder.Bottom | CellBorder.Right; - - sheet.Cells[0, 2] = "Bold Red"; - sheet.Cells[0, 2].Bold = true; - sheet.Cells[0, 2].TextColor = Color.Red; - - Cell cell = "BIU & Big & Blue"; - cell.Bold = true; - cell.Underline = true; - cell.Italic = true; - cell.TextColor = Color.Blue; - cell.FontSize = 18; - cell.Hyperlink = "https://github.com/mstum/Simplexcel"; - sheet.Cells[0, 3] = cell; - sheet.ColumnWidths[3] = 40; - - - sheet.Cells[0, 4] = 13; - sheet.Cells[0, 5] = 13.58; - - Cell cell2 = "Orange"; - cell2.Bold = true; - cell2.Italic = true; - cell2.TextColor = Color.Orange; - cell2.FontSize = 18; - sheet.Cells[0, 2] = cell2; - - sheet.Cells[0, 6] = "👪"; - sheet.Cells[0, 7] = "👨‍👩‍👧‍👦"; - - var i = 0; - foreach(var datum in _data ?? Enumerable.Empty()) - { - i++; - sheet.Cells[i, 0] = datum; - } - - wb.Add(sheet); - - var sheet2 = new Worksheet("Sheet 2"); - sheet2[0, 0] = "Sheet Number 2"; - sheet2[0, 0].Bold = true; - wb.Add(sheet2); - - return wb; - } - } -} \ No newline at end of file diff --git a/src/Simplexcel.MvcTestApp/Sample.cs b/src/Simplexcel.MvcTestApp/Sample.cs new file mode 100644 index 0000000..3be1d8c --- /dev/null +++ b/src/Simplexcel.MvcTestApp/Sample.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; + +namespace Simplexcel.MvcTestApp; + +public static class Sample +{ + public static Workbook GenerateWorkbook() + { + var wb = new Workbook + { + Title = "Workbook Title", + Author = "The Author", + }; + + var sheet = new Worksheet("Test"); + sheet.PageSetup.Orientation = Orientation.Landscape; + sheet.PageSetup.PrintRepeatRows = 2; + sheet.PageSetup.PrintRepeatColumns = 2; + + sheet.ColumnWidths[0] = 24.6; + + sheet.Cells["A1"] = "Test"; + sheet.Cells["A1"].FontName = "Arial Black"; + + sheet.Cells[0, 1] = "Another Test"; + sheet.Cells[0, 1].Border = CellBorder.Bottom | CellBorder.Right; + + sheet.Cells[0, 2] = "Bold Red"; + sheet.Cells[0, 2].Bold = true; + sheet.Cells[0, 2].TextColor = Color.Red; + + Cell cell = "BIU & Big & Blue"; + cell.Bold = true; + cell.Underline = true; + cell.Italic = true; + cell.TextColor = Color.Blue; + cell.FontSize = 18; + cell.Hyperlink = "https://github.com/mstum/Simplexcel"; + sheet.Cells[0, 3] = cell; + sheet.ColumnWidths[3] = 40; + + + sheet.Cells[0, 4] = 13; + sheet.Cells[0, 5] = 13.58; + + Cell cell2 = "Orange"; + cell2.Bold = true; + cell2.Italic = true; + cell2.TextColor = Color.Orange; + cell2.FontSize = 18; + sheet.Cells[0, 2] = cell2; + + sheet.Cells[0, 6] = "👪"; + sheet.Cells[0, 7] = "👨‍👩‍👧‍👦"; + + var i = 0; + foreach(var datum in Enumerable.Range(0, 55).Select(_ => Guid.NewGuid().ToString())) + { + i++; + sheet.Cells[i, 0] = datum; + } + + wb.Add(sheet); + + var sheet2 = new Worksheet("Sheet 2"); + sheet2[0, 0] = "Sheet Number 2"; + sheet2[0, 0].Bold = true; + wb.Add(sheet2); + + return wb; + } +} \ No newline at end of file From 07c901723415fc74738cbd3ec2ff1cf416a90026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Tue, 17 Dec 2024 23:46:00 +0100 Subject: [PATCH 04/12] Use file-scoped namespaces --- src/Simplexcel.TestApp/Program.cs | 453 +++-- src/Simplexcel.Tests/BugTests.cs | 37 +- src/Simplexcel.Tests/CellAddressTests.cs | 145 +- src/Simplexcel.Tests/CellCollectionTests.cs | 17 +- src/Simplexcel.Tests/CellTests.cs | 59 +- src/Simplexcel.Tests/ColRowCountTests.cs | 77 +- src/Simplexcel.Tests/WorksheetTests.cs | 75 +- src/Simplexcel/CellAddressHelper.cs | 139 +- src/Simplexcel/Cells/BuiltInCellFormat.cs | 101 +- src/Simplexcel/Cells/Cell.cs | 443 +++-- src/Simplexcel/Cells/CellAddress.cs | 83 +- src/Simplexcel/Cells/CellBorder.cs | 61 +- src/Simplexcel/Cells/CellCollection.cs | 129 +- src/Simplexcel/Cells/CellType.cs | 43 +- src/Simplexcel/Cells/GradientFill.cs | 71 +- src/Simplexcel/Cells/GradientStop.cs | 81 +- src/Simplexcel/Cells/HorizontalAlign.cs | 95 +- src/Simplexcel/Cells/IgnoredError.cs | 253 ++- src/Simplexcel/Cells/PatternFill.cs | 147 +- src/Simplexcel/Cells/PatternType.cs | 233 ++- src/Simplexcel/Cells/VerticalAlign.cs | 69 +- src/Simplexcel/Cells/XlsxColumnAttribute.cs | 77 +- src/Simplexcel/Color.cs | 1513 ++++++++--------- src/Simplexcel/ColumnWidthCollection.cs | 75 +- src/Simplexcel/ExtensionMethods.Internal.cs | 105 +- src/Simplexcel/ExtensionMethods.cs | 79 +- src/Simplexcel/LargeNumberHandlingMode.cs | 39 +- src/Simplexcel/PageSetup/Orientation.cs | 27 +- src/Simplexcel/PageSetup/PageBreak.cs | 61 +- src/Simplexcel/PageSetup/PageSetup.cs | 35 +- src/Simplexcel/Simplexcel.csproj | 4 +- src/Simplexcel/SimplexcelVersion.cs | 73 +- src/Simplexcel/Workbook.cs | 155 +- src/Simplexcel/Worksheet.Populate.cs | 217 ++- src/Simplexcel/Worksheet.cs | 409 +++-- src/Simplexcel/WorksheetView/Pane.cs | 63 +- src/Simplexcel/WorksheetView/PaneState.cs | 41 +- src/Simplexcel/WorksheetView/Panes.cs | 73 +- src/Simplexcel/WorksheetView/Selection.cs | 49 +- src/Simplexcel/WorksheetView/SheetView.cs | 129 +- src/Simplexcel/XlsxInternal/Namespaces.cs | 43 +- src/Simplexcel/XlsxInternal/Relationship.cs | 27 +- .../XlsxInternal/RelationshipCounter.cs | 19 +- src/Simplexcel/XlsxInternal/SharedStrings.cs | 129 +- .../XlsxInternal/SheetPackageInfo.cs | 37 +- src/Simplexcel/XlsxInternal/StyleWriter.cs | 561 +++--- src/Simplexcel/XlsxInternal/XlsxCell.cs | 45 +- src/Simplexcel/XlsxInternal/XlsxCellStyle.cs | 151 +- src/Simplexcel/XlsxInternal/XlsxCellTypes.cs | 67 +- .../XlsxInternal/XlsxExtensionMethods.cs | 35 +- src/Simplexcel/XlsxInternal/XlsxFont.cs | 103 +- .../XlsxInternal/XlsxIgnoredError.cs | 57 +- .../XlsxIgnoredErrorCollection.cs | 51 +- src/Simplexcel/XlsxInternal/XlsxPackage.cs | 165 +- src/Simplexcel/XlsxInternal/XlsxRow.cs | 41 +- src/Simplexcel/XlsxInternal/XlsxWriter.cs | 1049 ++++++------ src/Simplexcel/XlsxInternal/XmlFile.cs | 41 +- src/Simplexcel/XlsxInternal/ZipPackage.cs | 203 ++- 58 files changed, 4401 insertions(+), 4458 deletions(-) diff --git a/src/Simplexcel.TestApp/Program.cs b/src/Simplexcel.TestApp/Program.cs index c1a692e..1a18815 100644 --- a/src/Simplexcel.TestApp/Program.cs +++ b/src/Simplexcel.TestApp/Program.cs @@ -2,252 +2,251 @@ using System.Collections.Generic; using System.Linq; -namespace Simplexcel.TestApp +namespace Simplexcel.TestApp; + +class Program { - class Program + static void Main() { - static void Main() + var wb = new Workbook { - var wb = new Workbook - { - Title = "Workbook Title", - Author = "The Author" - }; - - var sheet = new Worksheet("Test"); - sheet.PageSetup.Orientation = Orientation.Landscape; - sheet.PageSetup.PrintRepeatRows = 2; - sheet.PageSetup.PrintRepeatColumns = 2; - - sheet.ColumnWidths[0] = 24.6; - - // Version Info - sheet["F11"] = "Version"; - sheet["G11"] = SimplexcelVersion.Version.ToString(); - sheet["F12"] = "VersionString"; - sheet["G12"] = SimplexcelVersion.VersionString; - sheet["F13"] = "PublicKeyToken"; - sheet["G13"] = SimplexcelVersion.PublicKeyToken; - sheet["F14"] = "PublicKey"; - sheet["G14"] = SimplexcelVersion.PublicKey; - - sheet.Cells["A1"] = "Test"; - sheet.Cells["A1"].FontName = "Arial Black"; - - sheet.Cells[0, 1] = "Another Test"; - sheet.Cells[0, 1].Border = CellBorder.Bottom | CellBorder.Right; - - sheet.Cells[0, 2] = "Bold Red"; - sheet.Cells[0, 2].Bold = true; - sheet.Cells[0, 2].TextColor = Color.Red; - - Cell cell = "BIU & Big & Blue"; - cell.Bold = true; - cell.Underline = true; - cell.Italic = true; - cell.TextColor = Color.Blue; - cell.FontSize = 18; - cell.Hyperlink = "https://github.com/mstum/Simplexcel"; - sheet.Cells[0, 3] = cell; - sheet.ColumnWidths[3] = 40; + Title = "Workbook Title", + Author = "The Author" + }; + + var sheet = new Worksheet("Test"); + sheet.PageSetup.Orientation = Orientation.Landscape; + sheet.PageSetup.PrintRepeatRows = 2; + sheet.PageSetup.PrintRepeatColumns = 2; + + sheet.ColumnWidths[0] = 24.6; + + // Version Info + sheet["F11"] = "Version"; + sheet["G11"] = SimplexcelVersion.Version.ToString(); + sheet["F12"] = "VersionString"; + sheet["G12"] = SimplexcelVersion.VersionString; + sheet["F13"] = "PublicKeyToken"; + sheet["G13"] = SimplexcelVersion.PublicKeyToken; + sheet["F14"] = "PublicKey"; + sheet["G14"] = SimplexcelVersion.PublicKey; + + sheet.Cells["A1"] = "Test"; + sheet.Cells["A1"].FontName = "Arial Black"; + + sheet.Cells[0, 1] = "Another Test"; + sheet.Cells[0, 1].Border = CellBorder.Bottom | CellBorder.Right; + + sheet.Cells[0, 2] = "Bold Red"; + sheet.Cells[0, 2].Bold = true; + sheet.Cells[0, 2].TextColor = Color.Red; + + Cell cell = "BIU & Big & Blue"; + cell.Bold = true; + cell.Underline = true; + cell.Italic = true; + cell.TextColor = Color.Blue; + cell.FontSize = 18; + cell.Hyperlink = "https://github.com/mstum/Simplexcel"; + sheet.Cells[0, 3] = cell; + sheet.ColumnWidths[3] = 40; - sheet.Cells[0, 4] = 13; - sheet.Cells[0, 5] = 13.58; + sheet.Cells[0, 4] = 13; + sheet.Cells[0, 5] = 13.58; - for (int i = 1; i < 55; i++) - { - sheet.Cells[1, i] = Guid.NewGuid().ToString("N"); - sheet.Cells[i, 0] = Guid.NewGuid().ToString("N"); - } + for (int i = 1; i < 55; i++) + { + sheet.Cells[1, i] = Guid.NewGuid().ToString("N"); + sheet.Cells[i, 0] = Guid.NewGuid().ToString("N"); + } - Cell cell2 = "Orange"; - cell2.Bold = true; - cell2.Italic = true; - cell2.TextColor = Color.Orange; - cell2.FontSize = 18; - sheet.Cells[0, 2] = cell2; - - // Note that Emoji only work in Excel 2013 or newer, not 2007 or 2010. This is an Excel limitation. - sheet.Cells[0, 6] = "👪"; - sheet.Cells[0, 7] = "👨‍👩‍👧‍👦"; - - var dateTime = new DateTime(2022, 4, 26, 13, 14, 15); - sheet.Cells["D4"] = dateTime; - sheet.Cells["D5"] = new Cell(CellType.Date, dateTime, BuiltInCellFormat.DateOnly); - sheet.Cells["D6"] = new Cell(CellType.Date, dateTime, BuiltInCellFormat.TimeOnly); - sheet.Cells["D7"] = new Cell(CellType.Date, dateTime, "yyyy\"_\"mm\"_\"dd\"_\"hh\"_\"mm\"_\"ss"); - sheet.Cells["D8"] = long.MaxValue; - sheet.Cells["D9"] = long.MinValue; - sheet.Cells["D10"] = decimal.MaxValue; - sheet.Cells["D11"] = decimal.MinValue; - - sheet.Cells["D12"] = 9999999999L; - sheet.Cells["D13"] = 99999999999L; - sheet.Cells["D14"] = 100000000000L; - sheet.Cells["D15"] = 100000000001L; - sheet.Cells["D16"] = 1000000000000L; - sheet.Cells["D17"] = 1000000000001L; - sheet.Cells["D18"] = Cell.LargeNumberPositiveLimit; - sheet.Cells["D19"] = Cell.LargeNumberPositiveLimit + 1; - sheet.Cells["D20"] = Cell.LargeNumberPositiveLimit - 1; - - sheet.Cells["D21"] = -9999999999L; - sheet.Cells["D22"] = -99999999999L; - sheet.Cells["D23"] = -100000000000L; - sheet.Cells["D24"] = -100000000001L; - sheet.Cells["D25"] = -1000000000000L; - sheet.Cells["D26"] = -1000000000001L; - sheet.Cells["D27"] = Cell.LargeNumberNegativeLimit; - sheet.Cells["D28"] = Cell.LargeNumberNegativeLimit + 1; - sheet.Cells["D29"] = Cell.LargeNumberNegativeLimit - 1; - sheet.LargeNumberHandlingMode = LargeNumberHandlingMode.StoreAsText; - - sheet.Cells["C5"] = "ThinDiagonalCrosshatch"; - sheet.Cells["C5"].Fill = new PatternFill { PatternType = PatternType.ThinDiagonalCrosshatch, PatternColor = Color.Yellow, BackgroundColor = Color.Violet }; - sheet.Cells["C5"].TextColor = Color.Green; - - sheet.Cells["C6"] = "ThickDiagonalCrosshatch"; - sheet.Cells["C6"].Fill = new PatternFill { PatternType = PatternType.ThickDiagonalCrosshatch, PatternColor = Color.Violet }; - sheet.Cells["C6"].TextColor = Color.Green; - - sheet.Cells["C7"] = "DiagonalCrosshatch"; - sheet.Cells["C7"].Fill = new PatternFill { PatternType = PatternType.DiagonalCrosshatch, BackgroundColor = Color.Violet }; - sheet.Cells["C7"].TextColor = Color.Green; - - sheet.Cells["C8"] = "FillSolid"; - sheet.Cells["C8"].Fill = new PatternFill { PatternType = PatternType.Solid, PatternColor = Color.Yellow, BackgroundColor = Color.Violet }; - sheet.Cells["C8"].TextColor = Color.Green; - - sheet.Cells["C9"] = "SetFillBgColor"; - sheet.Cells["C9"].Fill.BackgroundColor = Color.Navy; - sheet.Cells["C9"].TextColor = Color.Green; - - sheet.Cells["F5"] = "Hello,"; - sheet.Cells["G5"] = "World!"; - sheet.Cells["F5"].Fill.BackgroundColor = Color.Blue; - sheet.Cells["G5"].Fill.BackgroundColor = Color.Red; - - wb.Add(sheet); - - // Prime the Cache... - var populatedSheet = new Worksheet("Populate"); - populatedSheet.Populate(EnumeratePopulateTestData(), cacheTypeColumns: true); - - // ...and use the cache - populatedSheet = new Worksheet("Populate"); - populatedSheet.Populate(EnumeratePopulateTestData(), cacheTypeColumns: true); - wb.Add(populatedSheet); - - var frozenTopRowSheet = new Worksheet("Frozen Top Row") { AutoFilter = true }; - frozenTopRowSheet.Cells[0, 0] = "Header 1"; - frozenTopRowSheet.Cells[0, 1] = "Header 2"; - frozenTopRowSheet.Cells[0, 2] = "Header 3"; - foreach (var i in Enumerable.Range(1, 100)) - { - frozenTopRowSheet.Cells[i, 0] = "Value 1-" + i; - frozenTopRowSheet.Cells[i, 1] = "Value 2-" + i; - frozenTopRowSheet.Cells[i, 2] = "Value 3-" + i; - } - frozenTopRowSheet.FreezeTopRow(); - wb.Add(frozenTopRowSheet); - - var frozenLeftColumnSheet = new Worksheet("Frozen Left Column"); - frozenLeftColumnSheet.Cells[0, 0] = "Header 1"; - frozenLeftColumnSheet.Cells[1, 0] = "Header 2"; - frozenLeftColumnSheet.Cells[2, 0] = "Header 3"; - foreach (var i in Enumerable.Range(1, 100)) - { - frozenLeftColumnSheet.Cells[0, i] = "Value 1-" + i; - frozenLeftColumnSheet.Cells[1, i] = "Value 2-" + i; - frozenLeftColumnSheet.Cells[2, i] = "Value 3-" + i; - } - frozenLeftColumnSheet.FreezeLeftColumn(); - wb.Add(frozenLeftColumnSheet); + Cell cell2 = "Orange"; + cell2.Bold = true; + cell2.Italic = true; + cell2.TextColor = Color.Orange; + cell2.FontSize = 18; + sheet.Cells[0, 2] = cell2; + + // Note that Emoji only work in Excel 2013 or newer, not 2007 or 2010. This is an Excel limitation. + sheet.Cells[0, 6] = "👪"; + sheet.Cells[0, 7] = "👨‍👩‍👧‍👦"; + + var dateTime = new DateTime(2022, 4, 26, 13, 14, 15); + sheet.Cells["D4"] = dateTime; + sheet.Cells["D5"] = new Cell(CellType.Date, dateTime, BuiltInCellFormat.DateOnly); + sheet.Cells["D6"] = new Cell(CellType.Date, dateTime, BuiltInCellFormat.TimeOnly); + sheet.Cells["D7"] = new Cell(CellType.Date, dateTime, "yyyy\"_\"mm\"_\"dd\"_\"hh\"_\"mm\"_\"ss"); + sheet.Cells["D8"] = long.MaxValue; + sheet.Cells["D9"] = long.MinValue; + sheet.Cells["D10"] = decimal.MaxValue; + sheet.Cells["D11"] = decimal.MinValue; + + sheet.Cells["D12"] = 9999999999L; + sheet.Cells["D13"] = 99999999999L; + sheet.Cells["D14"] = 100000000000L; + sheet.Cells["D15"] = 100000000001L; + sheet.Cells["D16"] = 1000000000000L; + sheet.Cells["D17"] = 1000000000001L; + sheet.Cells["D18"] = Cell.LargeNumberPositiveLimit; + sheet.Cells["D19"] = Cell.LargeNumberPositiveLimit + 1; + sheet.Cells["D20"] = Cell.LargeNumberPositiveLimit - 1; + + sheet.Cells["D21"] = -9999999999L; + sheet.Cells["D22"] = -99999999999L; + sheet.Cells["D23"] = -100000000000L; + sheet.Cells["D24"] = -100000000001L; + sheet.Cells["D25"] = -1000000000000L; + sheet.Cells["D26"] = -1000000000001L; + sheet.Cells["D27"] = Cell.LargeNumberNegativeLimit; + sheet.Cells["D28"] = Cell.LargeNumberNegativeLimit + 1; + sheet.Cells["D29"] = Cell.LargeNumberNegativeLimit - 1; + sheet.LargeNumberHandlingMode = LargeNumberHandlingMode.StoreAsText; + + sheet.Cells["C5"] = "ThinDiagonalCrosshatch"; + sheet.Cells["C5"].Fill = new PatternFill { PatternType = PatternType.ThinDiagonalCrosshatch, PatternColor = Color.Yellow, BackgroundColor = Color.Violet }; + sheet.Cells["C5"].TextColor = Color.Green; + + sheet.Cells["C6"] = "ThickDiagonalCrosshatch"; + sheet.Cells["C6"].Fill = new PatternFill { PatternType = PatternType.ThickDiagonalCrosshatch, PatternColor = Color.Violet }; + sheet.Cells["C6"].TextColor = Color.Green; + + sheet.Cells["C7"] = "DiagonalCrosshatch"; + sheet.Cells["C7"].Fill = new PatternFill { PatternType = PatternType.DiagonalCrosshatch, BackgroundColor = Color.Violet }; + sheet.Cells["C7"].TextColor = Color.Green; + + sheet.Cells["C8"] = "FillSolid"; + sheet.Cells["C8"].Fill = new PatternFill { PatternType = PatternType.Solid, PatternColor = Color.Yellow, BackgroundColor = Color.Violet }; + sheet.Cells["C8"].TextColor = Color.Green; + + sheet.Cells["C9"] = "SetFillBgColor"; + sheet.Cells["C9"].Fill.BackgroundColor = Color.Navy; + sheet.Cells["C9"].TextColor = Color.Green; + + sheet.Cells["F5"] = "Hello,"; + sheet.Cells["G5"] = "World!"; + sheet.Cells["F5"].Fill.BackgroundColor = Color.Blue; + sheet.Cells["G5"].Fill.BackgroundColor = Color.Red; + + wb.Add(sheet); + + // Prime the Cache... + var populatedSheet = new Worksheet("Populate"); + populatedSheet.Populate(EnumeratePopulateTestData(), cacheTypeColumns: true); + + // ...and use the cache + populatedSheet = new Worksheet("Populate"); + populatedSheet.Populate(EnumeratePopulateTestData(), cacheTypeColumns: true); + wb.Add(populatedSheet); + + var frozenTopRowSheet = new Worksheet("Frozen Top Row") { AutoFilter = true }; + frozenTopRowSheet.Cells[0, 0] = "Header 1"; + frozenTopRowSheet.Cells[0, 1] = "Header 2"; + frozenTopRowSheet.Cells[0, 2] = "Header 3"; + foreach (var i in Enumerable.Range(1, 100)) + { + frozenTopRowSheet.Cells[i, 0] = "Value 1-" + i; + frozenTopRowSheet.Cells[i, 1] = "Value 2-" + i; + frozenTopRowSheet.Cells[i, 2] = "Value 3-" + i; + } + frozenTopRowSheet.FreezeTopRow(); + wb.Add(frozenTopRowSheet); + + var frozenLeftColumnSheet = new Worksheet("Frozen Left Column"); + frozenLeftColumnSheet.Cells[0, 0] = "Header 1"; + frozenLeftColumnSheet.Cells[1, 0] = "Header 2"; + frozenLeftColumnSheet.Cells[2, 0] = "Header 3"; + foreach (var i in Enumerable.Range(1, 100)) + { + frozenLeftColumnSheet.Cells[0, i] = "Value 1-" + i; + frozenLeftColumnSheet.Cells[1, i] = "Value 2-" + i; + frozenLeftColumnSheet.Cells[2, i] = "Value 3-" + i; + } + frozenLeftColumnSheet.FreezeLeftColumn(); + wb.Add(frozenLeftColumnSheet); - var pageBreakSheet = new Worksheet("Page Breaks"); - for (var row = 0; row < 20; row++) + var pageBreakSheet = new Worksheet("Page Breaks"); + for (var row = 0; row < 20; row++) + { + for (var col = 0; col < 20; col++) { - for (var col = 0; col < 20; col++) - { - pageBreakSheet.Cells[row, col] = $"Row {row} /Col {col}"; - } + pageBreakSheet.Cells[row, col] = $"Row {row} /Col {col}"; } - pageBreakSheet.InsertManualPageBreakAfterRow("B1"); - pageBreakSheet.InsertManualPageBreakAfterRow(5); - pageBreakSheet.InsertManualPageBreakAfterColumn("B8"); - pageBreakSheet.InsertManualPageBreakAfterColumn(16); - wb.Add(pageBreakSheet); - - var formulaSheet = new Worksheet("Formula"); - formulaSheet.Cells["A1"] = 5; - formulaSheet.Cells["A2"] = 10; - formulaSheet.Cells["A3"] = 15; - formulaSheet.Cells["B1"] = Cell.Formula("SUM(A:A)"); - formulaSheet.Cells["B2"] = Cell.Formula("SUM(A1:A2)"); - formulaSheet.Cells["B3"] = Cell.Formula("AVERAGE(A:A)"); - formulaSheet.Cells["B4"] = Cell.Formula("MEDIAN(A:A)"); - formulaSheet.Cells["C1"] = Cell.Formula("A1+B1"); - formulaSheet.Cells["C2"] = Cell.Formula("=SUM(B:B)"); - formulaSheet.Cells["C3"] = Cell.Formula("C5+A1"); - formulaSheet.Cells["C5"] = Cell.Formula("SUM(B:B)+C1"); - wb.Add(formulaSheet); - - var freezeTopLeftSheet = new Worksheet("FreezeTopLeft"); - for (var row = 0; row < 10; row++) - for (var col = 0; col < 10; col++) - { - freezeTopLeftSheet[row, col] = $"{row},{col}"; - } - freezeTopLeftSheet.FreezeTopLeft(2, 2); - wb.Add(freezeTopLeftSheet); - - wb.Save("compressed.xlsx", compress: true); - wb.Save("uncompressed.xlsx", compress: false); } - - private static IEnumerable EnumeratePopulateTestData() + pageBreakSheet.InsertManualPageBreakAfterRow("B1"); + pageBreakSheet.InsertManualPageBreakAfterRow(5); + pageBreakSheet.InsertManualPageBreakAfterColumn("B8"); + pageBreakSheet.InsertManualPageBreakAfterColumn(16); + wb.Add(pageBreakSheet); + + var formulaSheet = new Worksheet("Formula"); + formulaSheet.Cells["A1"] = 5; + formulaSheet.Cells["A2"] = 10; + formulaSheet.Cells["A3"] = 15; + formulaSheet.Cells["B1"] = Cell.Formula("SUM(A:A)"); + formulaSheet.Cells["B2"] = Cell.Formula("SUM(A1:A2)"); + formulaSheet.Cells["B3"] = Cell.Formula("AVERAGE(A:A)"); + formulaSheet.Cells["B4"] = Cell.Formula("MEDIAN(A:A)"); + formulaSheet.Cells["C1"] = Cell.Formula("A1+B1"); + formulaSheet.Cells["C2"] = Cell.Formula("=SUM(B:B)"); + formulaSheet.Cells["C3"] = Cell.Formula("C5+A1"); + formulaSheet.Cells["C5"] = Cell.Formula("SUM(B:B)+C1"); + wb.Add(formulaSheet); + + var freezeTopLeftSheet = new Worksheet("FreezeTopLeft"); + for (var row = 0; row < 10; row++) + for (var col = 0; col < 10; col++) { - var r = new Random(); + freezeTopLeftSheet[row, col] = $"{row},{col}"; + } + freezeTopLeftSheet.FreezeTopLeft(2, 2); + wb.Add(freezeTopLeftSheet); + + wb.Save("compressed.xlsx", compress: true); + wb.Save("uncompressed.xlsx", compress: false); + } + + private static IEnumerable EnumeratePopulateTestData() + { + var r = new Random(); - for (int i = 0; i < 500; i++) + for (int i = 0; i < 500; i++) + { + yield return new PopulateTestData { - yield return new PopulateTestData - { - Id = i, - CreatedUtc = new DateTime(2015, 1, 1, 1, 1, 1).AddDays(r.Next(1, 852)), - Name = "Item Number " + i, - Value = int.MaxValue - i, - Price = r.Next(800, 9400) * 0.52m, - Quantity = r.Next(4, 75) * 0.5m, - IgnoreMe = Guid.NewGuid().ToString() - }; - } + Id = i, + CreatedUtc = new DateTime(2015, 1, 1, 1, 1, 1).AddDays(r.Next(1, 852)), + Name = "Item Number " + i, + Value = int.MaxValue - i, + Price = r.Next(800, 9400) * 0.52m, + Quantity = r.Next(4, 75) * 0.5m, + IgnoreMe = Guid.NewGuid().ToString() + }; } + } - private abstract class PopulateTestDataBase - { - public int Id { get; set; } + private abstract class PopulateTestDataBase + { + public int Id { get; set; } - [XlsxColumn("Unit Price")] - public decimal Price { get; set; } + [XlsxColumn("Unit Price")] + public decimal Price { get; set; } - [XlsxColumn(null)] - public decimal Quantity { get; set; } - } + [XlsxColumn(null)] + public decimal Quantity { get; set; } + } - private class PopulateTestData : PopulateTestDataBase - { - public string Name { get; set; } + private class PopulateTestData : PopulateTestDataBase + { + public string Name { get; set; } - [XlsxColumn("")] - public long Value { get; set; } + [XlsxColumn("")] + public long Value { get; set; } - [XlsxColumn("Total")] - public decimal TotalPrice { get { return Price * Quantity; } } - public DateTime CreatedUtc { get; set; } + [XlsxColumn("Total")] + public decimal TotalPrice { get { return Price * Quantity; } } + public DateTime CreatedUtc { get; set; } - [XlsxIgnoreColumn] - public string IgnoreMe { get; set; } - } + [XlsxIgnoreColumn] + public string IgnoreMe { get; set; } } -} +} \ No newline at end of file diff --git a/src/Simplexcel.Tests/BugTests.cs b/src/Simplexcel.Tests/BugTests.cs index 08144b0..713bbbf 100644 --- a/src/Simplexcel.Tests/BugTests.cs +++ b/src/Simplexcel.Tests/BugTests.cs @@ -2,27 +2,26 @@ using System.Linq; using Xunit; -namespace Simplexcel.Tests +namespace Simplexcel.Tests; + +public class BugTests { - public class BugTests + /// + /// https://github.com/mstum/Simplexcel/issues/12 + /// + /// SharedStrings _sanitizeRegex makes "&" in CellStrings impossible + /// + [Fact] + public void Issue12_AmpersandInCellValues() { - /// - /// https://github.com/mstum/Simplexcel/issues/12 - /// - /// SharedStrings _sanitizeRegex makes "&" in CellStrings impossible - /// - [Fact] - public void Issue12_AmpersandInCellValues() - { - var sharedStrings = new SharedStrings(); - sharedStrings.GetStringIndex("Here & Now"); - sharedStrings.GetStringIndex("&"); - var xmlFile = sharedStrings.ToXmlFile(); + var sharedStrings = new SharedStrings(); + sharedStrings.GetStringIndex("Here & Now"); + sharedStrings.GetStringIndex("&"); + var xmlFile = sharedStrings.ToXmlFile(); - var nodeValues = xmlFile.Content.Descendants(Namespaces.x + "t").Select(x => x.Value).ToList(); + var nodeValues = xmlFile.Content.Descendants(Namespaces.x + "t").Select(x => x.Value).ToList(); - Assert.Contains("Here & Now", nodeValues); - Assert.Contains("&", nodeValues); - } + Assert.Contains("Here & Now", nodeValues); + Assert.Contains("&", nodeValues); } -} +} \ No newline at end of file diff --git a/src/Simplexcel.Tests/CellAddressTests.cs b/src/Simplexcel.Tests/CellAddressTests.cs index 081c06e..885d4de 100644 --- a/src/Simplexcel.Tests/CellAddressTests.cs +++ b/src/Simplexcel.Tests/CellAddressTests.cs @@ -1,78 +1,77 @@ using Xunit; -namespace Simplexcel.Tests +namespace Simplexcel.Tests; + +public class CellAddressTests { - public class CellAddressTests + [Fact] + public void CellAddress_ToString_ProperlyConverts() { - [Fact] - public void CellAddress_ToString_ProperlyConverts() - { - Assert.Equal("A1", new CellAddress(0, 0).ToString()); - Assert.Equal("B1", new CellAddress(0, 1).ToString()); - Assert.Equal("A2", new CellAddress(1, 0).ToString()); - Assert.Equal("B2", new CellAddress(1, 1).ToString()); - Assert.Equal("Z1", new CellAddress(0, 25).ToString()); - Assert.Equal("AA1", new CellAddress(0, 26).ToString()); - Assert.Equal("BA1", new CellAddress(0, 52).ToString()); - Assert.Equal("BZ1", new CellAddress(0, 77).ToString()); - Assert.Equal("CA1", new CellAddress(0, 78).ToString()); - Assert.Equal("IV1", new CellAddress(0, 255).ToString()); - Assert.Equal("CA1", new CellAddress(0, 78).ToString()); - Assert.Equal("ZZ1", new CellAddress(0, 701).ToString()); - Assert.Equal("AAA1", new CellAddress(0, 702).ToString()); - Assert.Equal("AAZ1", new CellAddress(0, 727).ToString()); - Assert.Equal("ABA1", new CellAddress(0, 728).ToString()); - Assert.Equal("XFD1", new CellAddress(0, 16383).ToString()); - Assert.Equal("ABA1000", new CellAddress(999, 728).ToString()); - } - - [Fact] - public void CellAdress_FromString_ProperlyParses() - { - Assert.Equal(0, new CellAddress("A1").Column); - Assert.Equal(0, new CellAddress("A1").Row); - - Assert.Equal(1, new CellAddress("B1").Column); - Assert.Equal(0, new CellAddress("B1").Row); - - Assert.Equal(0, new CellAddress("A2").Column); - Assert.Equal(1, new CellAddress("A2").Row); - - Assert.Equal(1, new CellAddress("B2").Column); - Assert.Equal(1, new CellAddress("B2").Row); - - Assert.Equal(25, new CellAddress("Z1").Column); - Assert.Equal(0, new CellAddress("Z1").Row); - - Assert.Equal(26, new CellAddress("AA1").Column); - Assert.Equal(0, new CellAddress("AA1").Row); - - Assert.Equal(52, new CellAddress("BA1").Column); - Assert.Equal(0, new CellAddress("BA1").Row); - - Assert.Equal(77, new CellAddress("BZ1").Column); - Assert.Equal(0, new CellAddress("BZ1").Row); - - Assert.Equal(78, new CellAddress("CA1").Column); - Assert.Equal(0, new CellAddress("CA1").Row); - - Assert.Equal(701, new CellAddress("ZZ1").Column); - Assert.Equal(0, new CellAddress("ZZ1").Row); - - Assert.Equal(702, new CellAddress("AAA1").Column); - Assert.Equal(0, new CellAddress("AAA1").Row); - - Assert.Equal(727, new CellAddress("AAZ1").Column); - Assert.Equal(0, new CellAddress("AAZ1").Row); - - Assert.Equal(728, new CellAddress("ABA1").Column); - Assert.Equal(0, new CellAddress("ABA1").Row); - - Assert.Equal(16383, new CellAddress("XFD1").Column); - Assert.Equal(0, new CellAddress("XFD1").Row); - - Assert.Equal(728, new CellAddress("ABA1000").Column); - Assert.Equal(999, new CellAddress("ABA1000").Row); - } + Assert.Equal("A1", new CellAddress(0, 0).ToString()); + Assert.Equal("B1", new CellAddress(0, 1).ToString()); + Assert.Equal("A2", new CellAddress(1, 0).ToString()); + Assert.Equal("B2", new CellAddress(1, 1).ToString()); + Assert.Equal("Z1", new CellAddress(0, 25).ToString()); + Assert.Equal("AA1", new CellAddress(0, 26).ToString()); + Assert.Equal("BA1", new CellAddress(0, 52).ToString()); + Assert.Equal("BZ1", new CellAddress(0, 77).ToString()); + Assert.Equal("CA1", new CellAddress(0, 78).ToString()); + Assert.Equal("IV1", new CellAddress(0, 255).ToString()); + Assert.Equal("CA1", new CellAddress(0, 78).ToString()); + Assert.Equal("ZZ1", new CellAddress(0, 701).ToString()); + Assert.Equal("AAA1", new CellAddress(0, 702).ToString()); + Assert.Equal("AAZ1", new CellAddress(0, 727).ToString()); + Assert.Equal("ABA1", new CellAddress(0, 728).ToString()); + Assert.Equal("XFD1", new CellAddress(0, 16383).ToString()); + Assert.Equal("ABA1000", new CellAddress(999, 728).ToString()); + } + + [Fact] + public void CellAdress_FromString_ProperlyParses() + { + Assert.Equal(0, new CellAddress("A1").Column); + Assert.Equal(0, new CellAddress("A1").Row); + + Assert.Equal(1, new CellAddress("B1").Column); + Assert.Equal(0, new CellAddress("B1").Row); + + Assert.Equal(0, new CellAddress("A2").Column); + Assert.Equal(1, new CellAddress("A2").Row); + + Assert.Equal(1, new CellAddress("B2").Column); + Assert.Equal(1, new CellAddress("B2").Row); + + Assert.Equal(25, new CellAddress("Z1").Column); + Assert.Equal(0, new CellAddress("Z1").Row); + + Assert.Equal(26, new CellAddress("AA1").Column); + Assert.Equal(0, new CellAddress("AA1").Row); + + Assert.Equal(52, new CellAddress("BA1").Column); + Assert.Equal(0, new CellAddress("BA1").Row); + + Assert.Equal(77, new CellAddress("BZ1").Column); + Assert.Equal(0, new CellAddress("BZ1").Row); + + Assert.Equal(78, new CellAddress("CA1").Column); + Assert.Equal(0, new CellAddress("CA1").Row); + + Assert.Equal(701, new CellAddress("ZZ1").Column); + Assert.Equal(0, new CellAddress("ZZ1").Row); + + Assert.Equal(702, new CellAddress("AAA1").Column); + Assert.Equal(0, new CellAddress("AAA1").Row); + + Assert.Equal(727, new CellAddress("AAZ1").Column); + Assert.Equal(0, new CellAddress("AAZ1").Row); + + Assert.Equal(728, new CellAddress("ABA1").Column); + Assert.Equal(0, new CellAddress("ABA1").Row); + + Assert.Equal(16383, new CellAddress("XFD1").Column); + Assert.Equal(0, new CellAddress("XFD1").Row); + + Assert.Equal(728, new CellAddress("ABA1000").Column); + Assert.Equal(999, new CellAddress("ABA1000").Row); } -} +} \ No newline at end of file diff --git a/src/Simplexcel.Tests/CellCollectionTests.cs b/src/Simplexcel.Tests/CellCollectionTests.cs index e0c1a7b..e6cfa79 100644 --- a/src/Simplexcel.Tests/CellCollectionTests.cs +++ b/src/Simplexcel.Tests/CellCollectionTests.cs @@ -1,15 +1,14 @@ using Xunit; -namespace Simplexcel.Tests +namespace Simplexcel.Tests; + +public class CellCollectionTests { - public class CellCollectionTests + [Fact] + public void EmptyCellCollection_AccessUninitializedCell_ImplicitlyCreatesCell() { - [Fact] - public void EmptyCellCollection_AccessUninitializedCell_ImplicitlyCreatesCell() - { - var cc = new CellCollection(); + var cc = new CellCollection(); - cc[0, 0].Bold = true; - } + cc[0, 0].Bold = true; } -} +} \ No newline at end of file diff --git a/src/Simplexcel.Tests/CellTests.cs b/src/Simplexcel.Tests/CellTests.cs index 10cf4e0..82f47c2 100644 --- a/src/Simplexcel.Tests/CellTests.cs +++ b/src/Simplexcel.Tests/CellTests.cs @@ -1,39 +1,38 @@ using Xunit; -namespace Simplexcel.Tests +namespace Simplexcel.Tests; + +public class CellTests { - public class CellTests + [Fact] + public void ImplicitCreation_FromLong_NoDecimalPlaces() { - [Fact] - public void ImplicitCreation_FromLong_NoDecimalPlaces() - { - Cell cell = 1234; - Assert.Equal(BuiltInCellFormat.NumberNoDecimalPlaces, cell.Format); - Assert.Equal(CellType.Number, cell.CellType); - } + Cell cell = 1234; + Assert.Equal(BuiltInCellFormat.NumberNoDecimalPlaces, cell.Format); + Assert.Equal(CellType.Number, cell.CellType); + } - [Fact] - public void ImplicitCreation_FromDouble_TwoDecimalPlaces() - { - Cell cell = 1234.56; - Assert.Equal(BuiltInCellFormat.NumberTwoDecimalPlaces, cell.Format); - Assert.Equal(CellType.Number, cell.CellType); + [Fact] + public void ImplicitCreation_FromDouble_TwoDecimalPlaces() + { + Cell cell = 1234.56; + Assert.Equal(BuiltInCellFormat.NumberTwoDecimalPlaces, cell.Format); + Assert.Equal(CellType.Number, cell.CellType); - } + } - [Fact] - public void ImplicitCreation_FromDecimal_TwoDecimalPlaces() - { - Cell cell = 1234.56m; - Assert.Equal(BuiltInCellFormat.NumberTwoDecimalPlaces, cell.Format); - Assert.Equal(CellType.Number, cell.CellType); - } + [Fact] + public void ImplicitCreation_FromDecimal_TwoDecimalPlaces() + { + Cell cell = 1234.56m; + Assert.Equal(BuiltInCellFormat.NumberTwoDecimalPlaces, cell.Format); + Assert.Equal(CellType.Number, cell.CellType); + } - [Fact] - public void ImplicitCreation_FromString_CellTypeText() - { - Cell cell = "1234"; - Assert.Equal(CellType.Text, cell.CellType); - } + [Fact] + public void ImplicitCreation_FromString_CellTypeText() + { + Cell cell = "1234"; + Assert.Equal(CellType.Text, cell.CellType); } -} +} \ No newline at end of file diff --git a/src/Simplexcel.Tests/ColRowCountTests.cs b/src/Simplexcel.Tests/ColRowCountTests.cs index 91aca02..c3c1755 100644 --- a/src/Simplexcel.Tests/ColRowCountTests.cs +++ b/src/Simplexcel.Tests/ColRowCountTests.cs @@ -1,48 +1,47 @@ using Xunit; -namespace Simplexcel.Tests +namespace Simplexcel.Tests; + +public class ColRowCountTests { - public class ColRowCountTests + [Fact] + public void EmptySheet_ColumnCount_Zero() { - [Fact] - public void EmptySheet_ColumnCount_Zero() - { - var sheet = new Worksheet("test"); - Assert.Equal(0, sheet.Cells.ColumnCount); - } + var sheet = new Worksheet("test"); + Assert.Equal(0, sheet.Cells.ColumnCount); + } - [Fact] - public void EmptySheet_RowCount_Zero() - { - var sheet = new Worksheet("test"); - Assert.Equal(0, sheet.Cells.RowCount); - } + [Fact] + public void EmptySheet_RowCount_Zero() + { + var sheet = new Worksheet("test"); + Assert.Equal(0, sheet.Cells.RowCount); + } - [Fact] - public void NoOffByOneError() - { - var sheet = new Worksheet("Test"); - sheet.Cells[0, 0] = "Test"; - Assert.Equal(1, sheet.Cells.RowCount); - Assert.Equal(1, sheet.Cells.ColumnCount); - } + [Fact] + public void NoOffByOneError() + { + var sheet = new Worksheet("Test"); + sheet.Cells[0, 0] = "Test"; + Assert.Equal(1, sheet.Cells.RowCount); + Assert.Equal(1, sheet.Cells.ColumnCount); + } - [Fact] - public void EmptyRows_RowCount_CountsEmpty() - { - var sheet = new Worksheet("Test"); - sheet.Cells[0, 0] = "Test"; - sheet.Cells[6, 0] = "Row Seven"; - Assert.Equal(7, sheet.Cells.RowCount); - } + [Fact] + public void EmptyRows_RowCount_CountsEmpty() + { + var sheet = new Worksheet("Test"); + sheet.Cells[0, 0] = "Test"; + sheet.Cells[6, 0] = "Row Seven"; + Assert.Equal(7, sheet.Cells.RowCount); + } - [Fact] - public void EmptyRows_ColumnCount_CountsEmpty() - { - var sheet = new Worksheet("Test"); - sheet.Cells[0, 0] = "Test"; - sheet.Cells[0, 6] = "Column Seven"; - Assert.Equal(7, sheet.Cells.ColumnCount); - } + [Fact] + public void EmptyRows_ColumnCount_CountsEmpty() + { + var sheet = new Worksheet("Test"); + sheet.Cells[0, 0] = "Test"; + sheet.Cells[0, 6] = "Column Seven"; + Assert.Equal(7, sheet.Cells.ColumnCount); } -} +} \ No newline at end of file diff --git a/src/Simplexcel.Tests/WorksheetTests.cs b/src/Simplexcel.Tests/WorksheetTests.cs index 4f8c310..6938ff6 100644 --- a/src/Simplexcel.Tests/WorksheetTests.cs +++ b/src/Simplexcel.Tests/WorksheetTests.cs @@ -1,52 +1,51 @@ using System; using Xunit; -namespace Simplexcel.Tests +namespace Simplexcel.Tests; + +public class WorksheetTests { - public class WorksheetTests + [Fact] + public void Worksheet_NameTooLong_ThrowsArgumentException() { - [Fact] - public void Worksheet_NameTooLong_ThrowsArgumentException() - { - var name = new string('x', Worksheet.MaxSheetNameLength + 1); + var name = new string('x', Worksheet.MaxSheetNameLength + 1); - Assert.Throws(() => new Worksheet(name)); - } + Assert.Throws(() => new Worksheet(name)); + } - [Fact] - public void Worksheet_NameInvalidChars_ThrowsArgumentException() + [Fact] + public void Worksheet_NameInvalidChars_ThrowsArgumentException() + { + foreach (var invalid in Worksheet.InvalidSheetNameChars) { - foreach (var invalid in Worksheet.InvalidSheetNameChars) - { - var name = "x" + invalid + "y"; - Assert.Throws(() => new Worksheet(name)); - } + var name = "x" + invalid + "y"; + Assert.Throws(() => new Worksheet(name)); } + } - [Fact] - public void Worksheet_EmptyName_ThrowsArgumentException() - { - Assert.Throws(() => new Worksheet(string.Empty)); - } + [Fact] + public void Worksheet_EmptyName_ThrowsArgumentException() + { + Assert.Throws(() => new Worksheet(string.Empty)); + } - [Fact] - public void Worksheet_NullName_ThrowsArgumentException() - { - Assert.Throws(() => new Worksheet(null)); - } + [Fact] + public void Worksheet_NullName_ThrowsArgumentException() + { + Assert.Throws(() => new Worksheet(null)); + } - [Fact] - public void FreezeTopLeft_NegativeRows_Throws() - { - var ws = new Worksheet("Foo"); - Assert.Throws("rows", () => ws.FreezeTopLeft(-1, 9)); - } + [Fact] + public void FreezeTopLeft_NegativeRows_Throws() + { + var ws = new Worksheet("Foo"); + Assert.Throws("rows", () => ws.FreezeTopLeft(-1, 9)); + } - [Fact] - public void FreezeTopLeft_NegativeColumns_Throws() - { - var ws = new Worksheet("Foo"); - Assert.Throws("columns", () => ws.FreezeTopLeft(9, -1)); - } + [Fact] + public void FreezeTopLeft_NegativeColumns_Throws() + { + var ws = new Worksheet("Foo"); + Assert.Throws("columns", () => ws.FreezeTopLeft(9, -1)); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/CellAddressHelper.cs b/src/Simplexcel/CellAddressHelper.cs index 02767a3..6a979dc 100644 --- a/src/Simplexcel/CellAddressHelper.cs +++ b/src/Simplexcel/CellAddressHelper.cs @@ -3,93 +3,92 @@ using System.Linq; using System.Text.RegularExpressions; -namespace Simplexcel +namespace Simplexcel; + +internal static class CellAddressHelper { - internal static class CellAddressHelper + private readonly static Regex _cellAddressRegex = new Regex(@"(?[a-z]+)(?[0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly static char[] _chars = new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; + + /// + /// Convert a zero-based row and column to an Excel Cell Reference, e.g. A1 for [0,0] + /// + /// + /// + /// + internal static string ColRowToReference(int col, int row) { - private readonly static Regex _cellAddressRegex = new Regex(@"(?[a-z]+)(?[0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private readonly static char[] _chars = new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; + return ColToReference(col) + (row + 1); + } - /// - /// Convert a zero-based row and column to an Excel Cell Reference, e.g. A1 for [0,0] - /// - /// - /// - /// - internal static string ColRowToReference(int col, int row) + /// + /// Convert a zero-based column number to the proper Reference in Excel (e.g, 0 = A) + /// + /// + /// + internal static string ColToReference(int col) + { + var dividend = (col + 1); + var columnName = string.Empty; + while (dividend > 0) { - return ColToReference(col) + (row + 1); + var modifier = (dividend - 1) % 26; + columnName = Convert.ToChar(65 + modifier) + columnName; + dividend = ((dividend - modifier) / 26); } + return columnName; + } - /// - /// Convert a zero-based column number to the proper Reference in Excel (e.g, 0 = A) - /// - /// - /// - internal static string ColToReference(int col) - { - var dividend = (col + 1); - var columnName = string.Empty; - while (dividend > 0) - { - var modifier = (dividend - 1) % 26; - columnName = Convert.ToChar(65 + modifier) + columnName; - dividend = ((dividend - modifier) / 26); - } - return columnName; - } + /// + /// Convert an Excel Cell Reference to a zero-based column/row representation (e.g., A1 => [0,0]) + /// + internal static void ReferenceToColRow(string reference, out int row, out int column) + { + row = column = 0; - /// - /// Convert an Excel Cell Reference to a zero-based column/row representation (e.g., A1 => [0,0]) - /// - internal static void ReferenceToColRow(string reference, out int row, out int column) + var parts = _cellAddressRegex.Match(reference); + if (parts.Success) { - row = column = 0; - - var parts = _cellAddressRegex.Match(reference); - if (parts.Success) + var rowMatch = parts.Groups["Row"]; + var colMatch = parts.Groups["Column"]; + if (rowMatch.Success && colMatch.Success) { - var rowMatch = parts.Groups["Row"]; - var colMatch = parts.Groups["Column"]; - if (rowMatch.Success && colMatch.Success) - { - row = Convert.ToInt32(rowMatch.Value) - 1; + row = Convert.ToInt32(rowMatch.Value) - 1; - string colString = colMatch.Value.ToLower(); - int col = 0; - for (int i = 0; i < colString.Length; i++) + string colString = colMatch.Value.ToLower(); + int col = 0; + for (int i = 0; i < colString.Length; i++) + { + char c = colString[colString.Length - i - 1]; + var ix = Array.IndexOf(_chars, c); + if (ix == -1) { - char c = colString[colString.Length - i - 1]; - var ix = Array.IndexOf(_chars, c); - if (ix == -1) - { - throw new ArgumentException("Cell Reference needs to be in Excel format, e.g. A1 or BA321. Invalid: " + reference, nameof(reference)); - } - - var ixo = ix + 1; - var multiplier = Math.Pow(26, i); - col += (int)(ixo * multiplier); + throw new ArgumentException("Cell Reference needs to be in Excel format, e.g. A1 or BA321. Invalid: " + reference, nameof(reference)); } - column = col - 1; + + var ixo = ix + 1; + var multiplier = Math.Pow(26, i); + col += (int)(ixo * multiplier); } - } - else - { - throw new ArgumentException("Cell Reference needs to be in Excel format, e.g. A1 or BA321. Invalid: " + reference, nameof(reference)); + column = col - 1; } } - - internal static IList DetermineRanges(ICollection cellAddresses) + else { - var result = new List(); + throw new ArgumentException("Cell Reference needs to be in Excel format, e.g. A1 or BA321. Invalid: " + reference, nameof(reference)); + } + } - // TODO: Actually support Ranges. Ranges are Rectangular, e.g., A1:B5 (TopLeft:BottomRight) - foreach (var ca in cellAddresses ?? Enumerable.Empty()) - { - result.Add(ca.ToString()); - } + internal static IList DetermineRanges(ICollection cellAddresses) + { + var result = new List(); - return result; + // TODO: Actually support Ranges. Ranges are Rectangular, e.g., A1:B5 (TopLeft:BottomRight) + foreach (var ca in cellAddresses ?? Enumerable.Empty()) + { + result.Add(ca.ToString()); } + + return result; } -} +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/BuiltInCellFormat.cs b/src/Simplexcel/Cells/BuiltInCellFormat.cs index 3c0ca72..34d77b7 100644 --- a/src/Simplexcel/Cells/BuiltInCellFormat.cs +++ b/src/Simplexcel/Cells/BuiltInCellFormat.cs @@ -1,53 +1,52 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// Excel Built-In Cell formats +/// +public static class BuiltInCellFormat { /// - /// Excel Built-In Cell formats - /// - public static class BuiltInCellFormat - { - /// - /// General - /// - public const string General = "General"; - - /// - /// 0 - /// - public const string NumberNoDecimalPlaces = "0"; - - /// - /// 0.00 - /// - public const string NumberTwoDecimalPlaces = "0.00"; - - /// - /// 0% - /// - public const string PercentNoDecimalPlaces = "0%"; - - /// - /// 0.00% - /// - public const string PercentTwoDecimalPlaces = "0.00%"; - - /// - /// @ - /// - public const string Text = "@"; - - /// - /// m/d/yy h:mm - /// - public const string DateAndTime = "m/d/yy h:mm"; - - /// - /// mm-dd-yy - /// - public const string DateOnly = "mm-dd-yy"; - - /// - /// h:mm - /// - public const string TimeOnly = "h:mm"; - } -} + /// General + /// + public const string General = "General"; + + /// + /// 0 + /// + public const string NumberNoDecimalPlaces = "0"; + + /// + /// 0.00 + /// + public const string NumberTwoDecimalPlaces = "0.00"; + + /// + /// 0% + /// + public const string PercentNoDecimalPlaces = "0%"; + + /// + /// 0.00% + /// + public const string PercentTwoDecimalPlaces = "0.00%"; + + /// + /// @ + /// + public const string Text = "@"; + + /// + /// m/d/yy h:mm + /// + public const string DateAndTime = "m/d/yy h:mm"; + + /// + /// mm-dd-yy + /// + public const string DateOnly = "mm-dd-yy"; + + /// + /// h:mm + /// + public const string TimeOnly = "h:mm"; +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/Cell.cs b/src/Simplexcel/Cells/Cell.cs index b08edf1..f8209fe 100644 --- a/src/Simplexcel/Cells/Cell.cs +++ b/src/Simplexcel/Cells/Cell.cs @@ -1,262 +1,261 @@ using System; using Simplexcel.XlsxInternal; -namespace Simplexcel +namespace Simplexcel; + +/// +/// A cell inside a Worksheet +/// +public sealed class Cell { + internal XlsxCellStyle XlsxCellStyle { get; } + + /// + /// Create a new Cell of the given . + /// You can also implicitly create a cell from a string or number. + /// + /// + public Cell(CellType cellType) : this(cellType, null, BuiltInCellFormat.General) { } + /// - /// A cell inside a Worksheet + /// Create a new Cell of the given , with the given value and format. For some common formats, see . + /// You can also implicitly create a cell from a string or number. /// - public sealed class Cell + /// + /// + /// + public Cell(CellType type, object value, string format) { - internal XlsxCellStyle XlsxCellStyle { get; } + XlsxCellStyle = new XlsxCellStyle + { + Format = format + }; - /// - /// Create a new Cell of the given . - /// You can also implicitly create a cell from a string or number. - /// - /// - public Cell(CellType cellType) : this(cellType, null, BuiltInCellFormat.General) { } + Value = value; + CellType = type; + IgnoredErrors = new IgnoredError(); + } - /// - /// Create a new Cell of the given , with the given value and format. For some common formats, see . - /// You can also implicitly create a cell from a string or number. - /// - /// - /// - /// - public Cell(CellType type, object value, string format) - { - XlsxCellStyle = new XlsxCellStyle - { - Format = format - }; + /// + /// The Excel Format for the cell, see + /// + public string Format + { + get { return XlsxCellStyle.Format; } + set { XlsxCellStyle.Format = value; } + } - Value = value; - CellType = type; - IgnoredErrors = new IgnoredError(); - } + /// + /// The border around the cell + /// + public CellBorder Border + { + get { return XlsxCellStyle.Border; } + set { XlsxCellStyle.Border = value; } + } - /// - /// The Excel Format for the cell, see - /// - public string Format - { - get { return XlsxCellStyle.Format; } - set { XlsxCellStyle.Format = value; } - } + /// + /// The name of the Font (Default: Calibri) + /// + public string FontName + { + get { return XlsxCellStyle.Font.Name; } + set { XlsxCellStyle.Font.Name = value; } + } - /// - /// The border around the cell - /// - public CellBorder Border - { - get { return XlsxCellStyle.Border; } - set { XlsxCellStyle.Border = value; } - } + /// + /// The Size of the Font (Default: 11) + /// + public int FontSize + { + get { return XlsxCellStyle.Font.Size; } + set { XlsxCellStyle.Font.Size = value; } + } - /// - /// The name of the Font (Default: Calibri) - /// - public string FontName - { - get { return XlsxCellStyle.Font.Name; } - set { XlsxCellStyle.Font.Name = value; } - } + /// + /// Should the text be bold? + /// + public bool Bold + { + get { return XlsxCellStyle.Font.Bold; } + set { XlsxCellStyle.Font.Bold = value; } + } - /// - /// The Size of the Font (Default: 11) - /// - public int FontSize - { - get { return XlsxCellStyle.Font.Size; } - set { XlsxCellStyle.Font.Size = value; } - } + /// + /// Should the text be italic? + /// + public bool Italic + { + get { return XlsxCellStyle.Font.Italic; } + set { XlsxCellStyle.Font.Italic = value; } + } - /// - /// Should the text be bold? - /// - public bool Bold - { - get { return XlsxCellStyle.Font.Bold; } - set { XlsxCellStyle.Font.Bold = value; } - } + /// + /// Should the text be underlined? + /// + public bool Underline + { + get { return XlsxCellStyle.Font.Underline; } + set { XlsxCellStyle.Font.Underline = value; } + } - /// - /// Should the text be italic? - /// - public bool Italic - { - get { return XlsxCellStyle.Font.Italic; } - set { XlsxCellStyle.Font.Italic = value; } - } + /// + /// The font color. + /// + public Color TextColor + { + get { return XlsxCellStyle.Font.TextColor; } + set { XlsxCellStyle.Font.TextColor = value; } + } - /// - /// Should the text be underlined? - /// - public bool Underline - { - get { return XlsxCellStyle.Font.Underline; } - set { XlsxCellStyle.Font.Underline = value; } - } + /// + /// The interior/fill color. + /// + public PatternFill Fill + { + get { return XlsxCellStyle.Fill; } + set { XlsxCellStyle.Fill = value; } + } - /// - /// The font color. - /// - public Color TextColor - { - get { return XlsxCellStyle.Font.TextColor; } - set { XlsxCellStyle.Font.TextColor = value; } - } + /// + /// The Horizontal Alignment of content within a Cell + /// + public HorizontalAlign HorizontalAlignment + { + get { return XlsxCellStyle.HorizontalAlignment; } + set { XlsxCellStyle.HorizontalAlignment = value; } + } - /// - /// The interior/fill color. - /// - public PatternFill Fill - { - get { return XlsxCellStyle.Fill; } - set { XlsxCellStyle.Fill = value; } - } + /// + /// The Vertical Alignment of content within this Cell + /// + public VerticalAlign VerticalAlignment + { + get { return XlsxCellStyle.VerticalAlignment; } + set { XlsxCellStyle.VerticalAlignment = value; } + } - /// - /// The Horizontal Alignment of content within a Cell - /// - public HorizontalAlign HorizontalAlignment - { - get { return XlsxCellStyle.HorizontalAlignment; } - set { XlsxCellStyle.HorizontalAlignment = value; } - } + /// + /// Any errors that are ignored in this Cell + /// + public IgnoredError IgnoredErrors { get; } - /// - /// The Vertical Alignment of content within this Cell - /// - public VerticalAlign VerticalAlignment - { - get { return XlsxCellStyle.VerticalAlignment; } - set { XlsxCellStyle.VerticalAlignment = value; } - } + /// + /// The Type of the cell. + /// + public CellType CellType { get; } - /// - /// Any errors that are ignored in this Cell - /// - public IgnoredError IgnoredErrors { get; } + /// + /// The Content of the cell. + /// + public object Value { get; set; } - /// - /// The Type of the cell. - /// - public CellType CellType { get; } + /// + /// Should this cell be a Hyperlink to something? + /// + public string Hyperlink { get; set; } - /// - /// The Content of the cell. - /// - public object Value { get; set; } + /// + /// Create a new that includes a Formula (e.g., SUM(A1:A5)). Do not include the initial = sign! + /// + /// The formula, without the initial = sign (so "SUM(A1:A5)", not "=SUM(A1:A5)") + /// + public static Cell Formula(string formula) + { + return new Cell(CellType.Formula, formula, BuiltInCellFormat.General); + } - /// - /// Should this cell be a Hyperlink to something? - /// - public string Hyperlink { get; set; } + /// + /// Create a new with a of Text from a string. + /// + /// + /// + public static implicit operator Cell(string value) + { + return new Cell(CellType.Text, value, BuiltInCellFormat.Text); + } - /// - /// Create a new that includes a Formula (e.g., SUM(A1:A5)). Do not include the initial = sign! - /// - /// The formula, without the initial = sign (so "SUM(A1:A5)", not "=SUM(A1:A5)") - /// - public static Cell Formula(string formula) - { - return new Cell(CellType.Formula, formula, BuiltInCellFormat.General); - } + /// + /// Create a new with a of Number (formatted without decimal places) from an integer. + /// + /// + /// + public static implicit operator Cell(long value) + { + return new Cell(CellType.Number, Convert.ToDecimal(value), BuiltInCellFormat.NumberNoDecimalPlaces); + } - /// - /// Create a new with a of Text from a string. - /// - /// - /// - public static implicit operator Cell(string value) - { - return new Cell(CellType.Text, value, BuiltInCellFormat.Text); - } + /// + /// Create a new with a of Number (formatted with 2 decimal places) places from a decimal. + /// + /// + /// + public static implicit operator Cell(Decimal value) + { + return new Cell(CellType.Number, value, BuiltInCellFormat.NumberTwoDecimalPlaces); + } - /// - /// Create a new with a of Number (formatted without decimal places) from an integer. - /// - /// - /// - public static implicit operator Cell(long value) - { - return new Cell(CellType.Number, Convert.ToDecimal(value), BuiltInCellFormat.NumberNoDecimalPlaces); - } + /// + /// Create a new with a of Number (formatted with 2 decimal places) places from a double. + /// + /// + /// + public static implicit operator Cell(double value) + { + return new Cell(CellType.Number, Convert.ToDecimal(value), BuiltInCellFormat.NumberTwoDecimalPlaces); + } + + /// + /// Create a new with a of Date, formatted as DateAndTime. + /// + /// + /// + public static implicit operator Cell(DateTime value) + { + return new Cell(CellType.Date, value, BuiltInCellFormat.DateAndTime); + } - /// - /// Create a new with a of Number (formatted with 2 decimal places) places from a decimal. - /// - /// - /// - public static implicit operator Cell(Decimal value) + /// + /// Create a Cell from the given object, trying to determine the best cell type/format. + /// + /// + /// + public static Cell FromObject(object val) + { + Cell cell; + if (val is sbyte || val is short || val is int || val is long || val is byte || val is uint || val is ushort || val is ulong) { - return new Cell(CellType.Number, value, BuiltInCellFormat.NumberTwoDecimalPlaces); + cell = new Cell(CellType.Number, Convert.ToDecimal(val), BuiltInCellFormat.NumberNoDecimalPlaces); } - - /// - /// Create a new with a of Number (formatted with 2 decimal places) places from a double. - /// - /// - /// - public static implicit operator Cell(double value) + else if (val is float || val is double || val is decimal) { - return new Cell(CellType.Number, Convert.ToDecimal(value), BuiltInCellFormat.NumberTwoDecimalPlaces); + cell = new Cell(CellType.Number, Convert.ToDecimal(val), BuiltInCellFormat.General); } - - /// - /// Create a new with a of Date, formatted as DateAndTime. - /// - /// - /// - public static implicit operator Cell(DateTime value) + else if (val is DateTime) { - return new Cell(CellType.Date, value, BuiltInCellFormat.DateAndTime); + cell = new Cell(CellType.Date, val, BuiltInCellFormat.DateAndTime); } - - /// - /// Create a Cell from the given object, trying to determine the best cell type/format. - /// - /// - /// - public static Cell FromObject(object val) + else { - Cell cell; - if (val is sbyte || val is short || val is int || val is long || val is byte || val is uint || val is ushort || val is ulong) - { - cell = new Cell(CellType.Number, Convert.ToDecimal(val), BuiltInCellFormat.NumberNoDecimalPlaces); - } - else if (val is float || val is double || val is decimal) - { - cell = new Cell(CellType.Number, Convert.ToDecimal(val), BuiltInCellFormat.General); - } - else if (val is DateTime) - { - cell = new Cell(CellType.Date, val, BuiltInCellFormat.DateAndTime); - } - else - { - cell = new Cell(CellType.Text, (val ?? String.Empty).ToString(), BuiltInCellFormat.Text); - } - return cell; + cell = new Cell(CellType.Text, (val ?? String.Empty).ToString(), BuiltInCellFormat.Text); } + return cell; + } - /// - /// The largest positive number Excel can handle before applies - /// - public static decimal LargeNumberPositiveLimit => 99999999999m; + /// + /// The largest positive number Excel can handle before applies + /// + public static decimal LargeNumberPositiveLimit => 99999999999m; - /// - /// The largest negative number Excel can handle before applies - /// - public static decimal LargeNumberNegativeLimit => -99999999999m; + /// + /// The largest negative number Excel can handle before applies + /// + public static decimal LargeNumberNegativeLimit => -99999999999m; - /// - /// Check if the given number is so large that would apply to it - /// - /// The number to check - /// - public static bool IsLargeNumber(decimal? number) => number.HasValue && (number.Value < LargeNumberNegativeLimit || number.Value > LargeNumberPositiveLimit); - } -} + /// + /// Check if the given number is so large that would apply to it + /// + /// The number to check + /// + public static bool IsLargeNumber(decimal? number) => number.HasValue && (number.Value < LargeNumberNegativeLimit || number.Value > LargeNumberPositiveLimit); +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/CellAddress.cs b/src/Simplexcel/Cells/CellAddress.cs index 278727f..00e7d9f 100644 --- a/src/Simplexcel/Cells/CellAddress.cs +++ b/src/Simplexcel/Cells/CellAddress.cs @@ -1,50 +1,49 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// The zero-based Address of a cell +/// +/// +/// This is zero-based, so cell A1 is Row,Column [0,0] +/// +public struct CellAddress { /// - /// The zero-based Address of a cell + /// The zero-based row /// - /// - /// This is zero-based, so cell A1 is Row,Column [0,0] - /// - public struct CellAddress - { - /// - /// The zero-based row - /// - public readonly int Row; + public readonly int Row; - /// - /// The zero-based column - /// - public readonly int Column; + /// + /// The zero-based column + /// + public readonly int Column; - /// - /// Create a Cell Adress given the zero-based row and column - /// - /// - /// - public CellAddress(int row, int column) - { - Row = row; - Column = column; - } + /// + /// Create a Cell Adress given the zero-based row and column + /// + /// + /// + public CellAddress(int row, int column) + { + Row = row; + Column = column; + } - /// - /// Create a CellAddress from a Reference like A1 - /// - /// - public CellAddress(string cellAddress) - { - CellAddressHelper.ReferenceToColRow(cellAddress, out Row, out Column); - } + /// + /// Create a CellAddress from a Reference like A1 + /// + /// + public CellAddress(string cellAddress) + { + CellAddressHelper.ReferenceToColRow(cellAddress, out Row, out Column); + } - /// - /// Convert this CellAddress into a Cell Reference, e.g. A1 for [0,0] - /// - /// - public override string ToString() - { - return CellAddressHelper.ColRowToReference(Column, Row); - } + /// + /// Convert this CellAddress into a Cell Reference, e.g. A1 for [0,0] + /// + /// + public override string ToString() + { + return CellAddressHelper.ColRowToReference(Column, Row); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/CellBorder.cs b/src/Simplexcel/Cells/CellBorder.cs index 44d95ee..c1d78b1 100644 --- a/src/Simplexcel/Cells/CellBorder.cs +++ b/src/Simplexcel/Cells/CellBorder.cs @@ -1,41 +1,40 @@ using System; -namespace Simplexcel +namespace Simplexcel; + +/// +/// Border around a cell +/// +[Flags] +public enum CellBorder { /// - /// Border around a cell + /// No Border /// - [Flags] - public enum CellBorder - { - /// - /// No Border - /// - None = 0, + None = 0, - /// - /// Border at the top - /// - Top = 1, + /// + /// Border at the top + /// + Top = 1, - /// - /// Border on the Right side - /// - Right = 2, + /// + /// Border on the Right side + /// + Right = 2, - /// - /// Border at the bottom - /// - Bottom = 4, + /// + /// Border at the bottom + /// + Bottom = 4, - /// - /// Border on the Left Side - /// - Left = 8, + /// + /// Border on the Left Side + /// + Left = 8, - /// - /// Borders on all four sides - /// - All = Top + Right + Bottom + Left - } -} + /// + /// Borders on all four sides + /// + All = Top + Right + Bottom + Left +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/CellCollection.cs b/src/Simplexcel/Cells/CellCollection.cs index 70950e6..6a5d0af 100644 --- a/src/Simplexcel/Cells/CellCollection.cs +++ b/src/Simplexcel/Cells/CellCollection.cs @@ -1,82 +1,81 @@ using System.Collections.Generic; using System.Linq; -namespace Simplexcel +namespace Simplexcel; + +/// +/// A Collection of Cells +/// +public sealed class CellCollection : IEnumerable> { + private readonly Dictionary _cells = new Dictionary(); + /// - /// A Collection of Cells + /// How many Rows are in the sheet? (This counts the maximum row index, so empty rows are counted if they are followed by another row) /// - public sealed class CellCollection : IEnumerable> + public int RowCount { - private readonly Dictionary _cells = new Dictionary(); - - /// - /// How many Rows are in the sheet? (This counts the maximum row index, so empty rows are counted if they are followed by another row) - /// - public int RowCount - { - get { return _cells.Count > 0 ? _cells.Keys.Max(k => k.Row) + 1 : 0; } - } + get { return _cells.Count > 0 ? _cells.Keys.Max(k => k.Row) + 1 : 0; } + } - /// - /// How many Columns are in the sheet? (This counts the rightmost column, so empty columns are counted if they are followed by another column) - /// - public int ColumnCount - { - get { return _cells.Count > 0 ? _cells.Keys.Max(k => k.Column) + 1 : 0; } - } + /// + /// How many Columns are in the sheet? (This counts the rightmost column, so empty columns are counted if they are followed by another column) + /// + public int ColumnCount + { + get { return _cells.Count > 0 ? _cells.Keys.Max(k => k.Column) + 1 : 0; } + } - /// - /// Get the cell with the given cell reference, e.g. Get the cell "A1". May return NULL. - /// - /// - /// The Cell, or NULL of the Cell hasn't been created yet. - public Cell this[string address] - { - get { return this[new CellAddress(address)]; } - set { this[new CellAddress(address)] = value; } - } + /// + /// Get the cell with the given cell reference, e.g. Get the cell "A1". May return NULL. + /// + /// + /// The Cell, or NULL of the Cell hasn't been created yet. + public Cell this[string address] + { + get { return this[new CellAddress(address)]; } + set { this[new CellAddress(address)] = value; } + } - /// - /// Get the cell with the given zero based row and column, e.g. [0,0] returns the A1 cell. May return NULL. - /// - /// - /// - /// The Cell, or NULL of the Cell hasn't been created yet. - public Cell this[int row, int column] - { - get { return this[new CellAddress(row, column)]; } - set { this[new CellAddress(row, column)] = value; } - } + /// + /// Get the cell with the given zero based row and column, e.g. [0,0] returns the A1 cell. May return NULL. + /// + /// + /// + /// The Cell, or NULL of the Cell hasn't been created yet. + public Cell this[int row, int column] + { + get { return this[new CellAddress(row, column)]; } + set { this[new CellAddress(row, column)] = value; } + } - private Cell this[CellAddress key] + private Cell this[CellAddress key] + { + get { - get - { - if (!_cells.ContainsKey(key)) - _cells[key] = string.Empty; + if (!_cells.ContainsKey(key)) + _cells[key] = string.Empty; - return _cells[key]; - } - set { _cells[key] = value; } + return _cells[key]; } + set { _cells[key] = value; } + } - /// - /// Enumerate over the Cells in this Collection - /// - /// - public IEnumerator> GetEnumerator() - { - return _cells.GetEnumerator(); - } + /// + /// Enumerate over the Cells in this Collection + /// + /// + public IEnumerator> GetEnumerator() + { + return _cells.GetEnumerator(); + } - /// - /// Enumerate over the Cells in this Collection - /// - /// - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + /// Enumerate over the Cells in this Collection + /// + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/CellType.cs b/src/Simplexcel/Cells/CellType.cs index dbcb7f1..35312cc 100644 --- a/src/Simplexcel/Cells/CellType.cs +++ b/src/Simplexcel/Cells/CellType.cs @@ -1,28 +1,27 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// The Type of a Cell, important for handling the values corrently (e.g., Excel not converting long numbers into scientific notation or removing leading zeroes) +/// +public enum CellType { /// - /// The Type of a Cell, important for handling the values corrently (e.g., Excel not converting long numbers into scientific notation or removing leading zeroes) + /// Cell contains text, so use it exactly as specified /// - public enum CellType - { - /// - /// Cell contains text, so use it exactly as specified - /// - Text, + Text, - /// - /// Cell contains a number (may strip leading zeroes but sorts properly) - /// - Number, + /// + /// Cell contains a number (may strip leading zeroes but sorts properly) + /// + Number, - /// - /// Cell contains a date, must be greater than 01/01/0100 - /// - Date, + /// + /// Cell contains a date, must be greater than 01/01/0100 + /// + Date, - /// - /// Cell contains a Formula (e.g., "SUM(A1:A10)") - /// - Formula - } -} + /// + /// Cell contains a Formula (e.g., "SUM(A1:A10)") + /// + Formula +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/GradientFill.cs b/src/Simplexcel/Cells/GradientFill.cs index 1d51ded..9bac03f 100644 --- a/src/Simplexcel/Cells/GradientFill.cs +++ b/src/Simplexcel/Cells/GradientFill.cs @@ -2,50 +2,49 @@ using System.Collections.Generic; using System.Linq; -namespace Simplexcel +namespace Simplexcel; + +// TODO: Not Yet Implemented +internal class GradientFill : IEquatable { - // TODO: Not Yet Implemented - internal class GradientFill : IEquatable - { - public IList Stops { get; } + public IList Stops { get; } - public GradientFill() - { - Stops = new List(); - } + public GradientFill() + { + Stops = new List(); + } - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(GradientFill)) return false; - return Equals((GradientFill)obj); - } + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(GradientFill)) return false; + return Equals((GradientFill)obj); + } - public bool Equals(GradientFill other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return other.Stops.SequenceEqual(Stops); - } + public bool Equals(GradientFill other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other.Stops.SequenceEqual(Stops); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - int result = Stops.GetCollectionHashCode(); - return result; - } + int result = Stops.GetCollectionHashCode(); + return result; } + } - public static bool operator ==(GradientFill left, GradientFill right) - { - return Equals(left, right); - } + public static bool operator ==(GradientFill left, GradientFill right) + { + return Equals(left, right); + } - public static bool operator !=(GradientFill left, GradientFill right) - { - return !Equals(left, right); - } + public static bool operator !=(GradientFill left, GradientFill right) + { + return !Equals(left, right); } } \ No newline at end of file diff --git a/src/Simplexcel/Cells/GradientStop.cs b/src/Simplexcel/Cells/GradientStop.cs index 5e44651..dad3d6e 100644 --- a/src/Simplexcel/Cells/GradientStop.cs +++ b/src/Simplexcel/Cells/GradientStop.cs @@ -1,53 +1,52 @@ using System; -namespace Simplexcel +namespace Simplexcel; + +internal class GradientStop : IEquatable { - internal class GradientStop : IEquatable - { - /// - /// The position indicated here indicates the point where the color is pure. - /// - public double Position { get; set; } + /// + /// The position indicated here indicates the point where the color is pure. + /// + public double Position { get; set; } - /// - /// The pure color of this Gradient Stop - /// - public Color Color { get; set; } + /// + /// The pure color of this Gradient Stop + /// + public Color Color { get; set; } - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(GradientStop)) return false; - return Equals((GradientStop)obj); - } + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(GradientStop)) return false; + return Equals((GradientStop)obj); + } - public bool Equals(GradientStop other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.Position, Position) - && Equals(other.Color, Color); - } + public bool Equals(GradientStop other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(other.Position, Position) + && Equals(other.Color, Color); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - int result = Position.GetHashCode(); - result = (result * 397) ^ Color.GetHashCode(); - return result; - } + int result = Position.GetHashCode(); + result = (result * 397) ^ Color.GetHashCode(); + return result; } + } - public static bool operator ==(GradientStop left, GradientStop right) - { - return Equals(left, right); - } + public static bool operator ==(GradientStop left, GradientStop right) + { + return Equals(left, right); + } - public static bool operator !=(GradientStop left, GradientStop right) - { - return !Equals(left, right); - } + public static bool operator !=(GradientStop left, GradientStop right) + { + return !Equals(left, right); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/HorizontalAlign.cs b/src/Simplexcel/Cells/HorizontalAlign.cs index d62aff7..8ce2bc8 100644 --- a/src/Simplexcel/Cells/HorizontalAlign.cs +++ b/src/Simplexcel/Cells/HorizontalAlign.cs @@ -1,53 +1,52 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// The Horizontal Alignment of content within a Cell +/// +public enum HorizontalAlign { /// - /// The Horizontal Alignment of content within a Cell + /// No specific alignment, use Excel's default + /// + None = 0, + + /// + /// The horizontal alignment is general-aligned. Text data + /// is left-aligned. Numbers, dates, and times are rightaligned. + /// Boolean types are centered. Changing the + /// alignment does not change the type of data. + /// + General, + + /// + /// The horizontal alignment is left-aligned, even in Right-to-Left mode. + /// + Left, + + /// + /// The horizontal alignment is centered, meaning the text is centered across the cell. + /// + Center, + + /// + /// The horizontal alignment is right-aligned, meaning that + /// cell contents are aligned at the right edge of the cell, + /// even in Right-to-Left mode. + /// + Right, + + //Fill, + + /// + /// The horizontal alignment is justified (flush left and + /// right). For each line of text, aligns each line of the + /// wrapped text in a cell to the right and left (except the + /// last line). If no single line of text wraps in the cell, then + /// the text is not justified. /// - public enum HorizontalAlign - { - /// - /// No specific alignment, use Excel's default - /// - None = 0, - - /// - /// The horizontal alignment is general-aligned. Text data - /// is left-aligned. Numbers, dates, and times are rightaligned. - /// Boolean types are centered. Changing the - /// alignment does not change the type of data. - /// - General, - - /// - /// The horizontal alignment is left-aligned, even in Right-to-Left mode. - /// - Left, - - /// - /// The horizontal alignment is centered, meaning the text is centered across the cell. - /// - Center, - - /// - /// The horizontal alignment is right-aligned, meaning that - /// cell contents are aligned at the right edge of the cell, - /// even in Right-to-Left mode. - /// - Right, - - //Fill, - - /// - /// The horizontal alignment is justified (flush left and - /// right). For each line of text, aligns each line of the - /// wrapped text in a cell to the right and left (except the - /// last line). If no single line of text wraps in the cell, then - /// the text is not justified. - /// - Justify, + Justify, - //CenterContinuous, + //CenterContinuous, - //Distributed - } -} + //Distributed +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/IgnoredError.cs b/src/Simplexcel/Cells/IgnoredError.cs index abb96bb..26bb855 100644 --- a/src/Simplexcel/Cells/IgnoredError.cs +++ b/src/Simplexcel/Cells/IgnoredError.cs @@ -1,132 +1,131 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// Information about Errors that are being ignored in a cell +/// +public sealed class IgnoredError { /// - /// Information about Errors that are being ignored in a cell + /// Ignore errors when numbers are formatted as text or are preceded by an apostrophe. + /// + public bool NumberStoredAsText { get; set; } + + /// + /// Ignore errors when cells contain a value different from a calculated column formula. + /// In other words, for a calculated column, a cell in that column is considered to have + /// an error if its formula is different from the calculated column formula, or doesn't + /// contain a formula at all. + /// + public bool CalculatedColumn { get; set; } + + /// + /// Ignore errors when formulas refer to empty cells + /// + public bool EmptyCellReference { get; set; } + + /// + /// Ignore errors when cells contain formulas that result in an error. + /// + public bool EvalError { get; set; } + + /// + /// Ignore errors when a formula in a region of your worksheet differs from other formulas in the same region. + /// + public bool Formula { get; set; } + + /// + /// Ignore errors when formulas omit certain cells in a region. + /// + public bool FormulaRange { get; set; } + + /// + /// Ignore errors when a cell's value in a Table does not comply with the Data Validation rules specified. + /// + public bool ListDataValidation { get; set; } + + /// + /// Ignore errors when formulas contain text formatted cells with years represented as 2 digits. + /// + public bool TwoDigitTextYear { get; set; } + + /// + /// Ignore errors when unlocked cells contain formulas. + /// + public bool UnlockedFormula { get; set; } + + /// + /// Is this instance different from the default (does it need an ignoredError element?) + /// + internal bool IsDifferentFromDefault => GetHashCode() != 0; + + /// + /// Get the HashCode for this instance, which is a unique combination based on the boolean properties + /// + /// + public override int GetHashCode() + { + // This is like a bit field, since an int is 32-Bit and this class is all booleans + var result = 0; + if (EvalError) { result |= (1 << 0); } + if (TwoDigitTextYear) { result |= (1 << 1); } + if (NumberStoredAsText) { result |= (1 << 2); } + if (Formula) { result |= (1 << 3); } + if (FormulaRange) { result |= (1 << 4); } + if (UnlockedFormula) { result |= (1 << 5); } + if (EmptyCellReference) { result |= (1 << 6); } + if (ListDataValidation) { result |= (1 << 7); } + if (CalculatedColumn) { result |= (1 << 8); } + return result; + } + + /// + /// Compare this to another + /// + /// + /// + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(IgnoredError)) return false; + return Equals((IgnoredError)obj); + } + + /// + /// Compare this to another + /// + /// + /// + public bool Equals(IgnoredError other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + + var thisId = GetHashCode(); + var otherId = other.GetHashCode(); + + return otherId.Equals(thisId); + } + + /// + /// Compare a to another + /// + /// + /// + /// + public static bool operator ==(IgnoredError left, IgnoredError right) + { + return Equals(left, right); + } + + /// + /// Compare a to another /// - public sealed class IgnoredError + /// + /// + /// + public static bool operator !=(IgnoredError left, IgnoredError right) { - /// - /// Ignore errors when numbers are formatted as text or are preceded by an apostrophe. - /// - public bool NumberStoredAsText { get; set; } - - /// - /// Ignore errors when cells contain a value different from a calculated column formula. - /// In other words, for a calculated column, a cell in that column is considered to have - /// an error if its formula is different from the calculated column formula, or doesn't - /// contain a formula at all. - /// - public bool CalculatedColumn { get; set; } - - /// - /// Ignore errors when formulas refer to empty cells - /// - public bool EmptyCellReference { get; set; } - - /// - /// Ignore errors when cells contain formulas that result in an error. - /// - public bool EvalError { get; set; } - - /// - /// Ignore errors when a formula in a region of your worksheet differs from other formulas in the same region. - /// - public bool Formula { get; set; } - - /// - /// Ignore errors when formulas omit certain cells in a region. - /// - public bool FormulaRange { get; set; } - - /// - /// Ignore errors when a cell's value in a Table does not comply with the Data Validation rules specified. - /// - public bool ListDataValidation { get; set; } - - /// - /// Ignore errors when formulas contain text formatted cells with years represented as 2 digits. - /// - public bool TwoDigitTextYear { get; set; } - - /// - /// Ignore errors when unlocked cells contain formulas. - /// - public bool UnlockedFormula { get; set; } - - /// - /// Is this instance different from the default (does it need an ignoredError element?) - /// - internal bool IsDifferentFromDefault => GetHashCode() != 0; - - /// - /// Get the HashCode for this instance, which is a unique combination based on the boolean properties - /// - /// - public override int GetHashCode() - { - // This is like a bit field, since an int is 32-Bit and this class is all booleans - var result = 0; - if (EvalError) { result |= (1 << 0); } - if (TwoDigitTextYear) { result |= (1 << 1); } - if (NumberStoredAsText) { result |= (1 << 2); } - if (Formula) { result |= (1 << 3); } - if (FormulaRange) { result |= (1 << 4); } - if (UnlockedFormula) { result |= (1 << 5); } - if (EmptyCellReference) { result |= (1 << 6); } - if (ListDataValidation) { result |= (1 << 7); } - if (CalculatedColumn) { result |= (1 << 8); } - return result; - } - - /// - /// Compare this to another - /// - /// - /// - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(IgnoredError)) return false; - return Equals((IgnoredError)obj); - } - - /// - /// Compare this to another - /// - /// - /// - public bool Equals(IgnoredError other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - - var thisId = GetHashCode(); - var otherId = other.GetHashCode(); - - return otherId.Equals(thisId); - } - - /// - /// Compare a to another - /// - /// - /// - /// - public static bool operator ==(IgnoredError left, IgnoredError right) - { - return Equals(left, right); - } - - /// - /// Compare a to another - /// - /// - /// - /// - public static bool operator !=(IgnoredError left, IgnoredError right) - { - return !Equals(left, right); - } + return !Equals(left, right); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/PatternFill.cs b/src/Simplexcel/Cells/PatternFill.cs index 10bf161..14506f0 100644 --- a/src/Simplexcel/Cells/PatternFill.cs +++ b/src/Simplexcel/Cells/PatternFill.cs @@ -1,95 +1,94 @@ using System; -namespace Simplexcel +namespace Simplexcel; + +/// +/// A Pattern Fill of a Cell. +/// +public class PatternFill : IEquatable { - /// - /// A Pattern Fill of a Cell. - /// - public class PatternFill : IEquatable - { - // TODO: Add a Gradient Fill to this. - // This isn't the structure in the XML, but that's how Excel Presents it, as a "Fill Effect" + // TODO: Add a Gradient Fill to this. + // This isn't the structure in the XML, but that's how Excel Presents it, as a "Fill Effect" - bool firstTimeSet = false; - private Color? _bgColor; + bool firstTimeSet = false; + private Color? _bgColor; - /// - /// The type of Fill Pattern to use. - /// Refer to the documentation for a list of each pattern. - /// - public PatternType PatternType { get; set; } + /// + /// The type of Fill Pattern to use. + /// Refer to the documentation for a list of each pattern. + /// + public PatternType PatternType { get; set; } - /// - /// The Background Color of the cell - /// - public Color? PatternColor { get; set; } + /// + /// The Background Color of the cell + /// + public Color? PatternColor { get; set; } - /// - /// The Background Color of the Fill. - /// (No effect if is ) - /// - public Color? BackgroundColor + /// + /// The Background Color of the Fill. + /// (No effect if is ) + /// + public Color? BackgroundColor + { + get => _bgColor; + set { - get => _bgColor; - set - { - _bgColor = value; + _bgColor = value; - // PatternType defaults to None, but the first time we set a background color, - // set it to solid as the user likely wants the background color to show. - // Further modifications won't change the PatternType, as this is now a delibrate setting - if (_bgColor.HasValue && PatternType == PatternType.None && !firstTimeSet) - { - PatternType = PatternType.Solid; - firstTimeSet = true; - } + // PatternType defaults to None, but the first time we set a background color, + // set it to solid as the user likely wants the background color to show. + // Further modifications won't change the PatternType, as this is now a delibrate setting + if (_bgColor.HasValue && PatternType == PatternType.None && !firstTimeSet) + { + PatternType = PatternType.Solid; + firstTimeSet = true; } } + } - /// - /// Compare to another object. - /// - public override bool Equals(object obj) - => Equals(obj as PatternFill); + /// + /// Compare to another object. + /// + public override bool Equals(object obj) + => Equals(obj as PatternFill); - /// - /// Compare to another object. - /// - public bool Equals(PatternFill other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.PatternType, PatternType) - && Equals(other.PatternColor, PatternColor) - && Equals(other.BackgroundColor, BackgroundColor); - } + /// + /// Compare to another object. + /// + public bool Equals(PatternFill other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(other.PatternType, PatternType) + && Equals(other.PatternColor, PatternColor) + && Equals(other.BackgroundColor, BackgroundColor); + } - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked { - unchecked + int result = PatternType.GetHashCode(); + if (PatternColor.HasValue) { - int result = PatternType.GetHashCode(); - if (PatternColor.HasValue) - { - result = (result * 397) ^ PatternColor.GetHashCode(); - } - if (BackgroundColor.HasValue) - { - result = (result * 397) ^ BackgroundColor.GetHashCode(); - } - return result; + result = (result * 397) ^ PatternColor.GetHashCode(); } + if (BackgroundColor.HasValue) + { + result = (result * 397) ^ BackgroundColor.GetHashCode(); + } + return result; } + } - /// - /// Check whether the objects and are Equal. - /// - public static bool operator ==(PatternFill left, PatternFill right) => Equals(left, right); + /// + /// Check whether the objects and are Equal. + /// + public static bool operator ==(PatternFill left, PatternFill right) => Equals(left, right); - /// - /// Check whether the objects and are not Equal. - /// - public static bool operator !=(PatternFill left, PatternFill right) => !Equals(left, right); - } + /// + /// Check whether the objects and are not Equal. + /// + public static bool operator !=(PatternFill left, PatternFill right) => !Equals(left, right); } \ No newline at end of file diff --git a/src/Simplexcel/Cells/PatternType.cs b/src/Simplexcel/Cells/PatternType.cs index 428ad6b..8718bb5 100644 --- a/src/Simplexcel/Cells/PatternType.cs +++ b/src/Simplexcel/Cells/PatternType.cs @@ -1,120 +1,119 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// The Type of the pattern in a . +/// +public enum PatternType { /// - /// The Type of the pattern in a . - /// - public enum PatternType - { - /// - /// none - /// - None = 0, - - /// - /// solid - /// - Solid, - - /// - /// 75% Gray - /// darkGray - /// - Gray750, - - /// - /// 50% Gray - /// mediumGray - /// - Gray500, - - /// - /// 25% Gray - /// lightGray - /// - Gray250, - - /// - /// 12.5% Gray - /// gray125 - /// - Gray125, - - /// - /// 6.25% Gray - /// gray0625 - /// - Gray0625, + /// none + /// + None = 0, + + /// + /// solid + /// + Solid, + + /// + /// 75% Gray + /// darkGray + /// + Gray750, + + /// + /// 50% Gray + /// mediumGray + /// + Gray500, + + /// + /// 25% Gray + /// lightGray + /// + Gray250, + + /// + /// 12.5% Gray + /// gray125 + /// + Gray125, + + /// + /// 6.25% Gray + /// gray0625 + /// + Gray0625, - /// - /// Horizontal Stripe - /// darkHorizontal - /// - HorizontalStripe, - - /// - /// Vertical Stripe - /// darkVertical - /// - VerticalStripe, - - /// - /// Reverse Diagonal Stripe - /// darkDown - /// - ReverseDiagonalStripe, - - /// - /// Diagonal Stripe - /// darkUp - /// - DiagonalStripe, - - /// - /// Diagonal Crosshatch - /// darkGrid - /// - DiagonalCrosshatch, - - /// - /// Thick Diagonal Crosshatch - /// darkTrellis - /// - ThickDiagonalCrosshatch, - - /// - /// Thin Horizontal Stripe - /// lightHorizontal - /// - ThinHorizontalStripe, - - /// - /// Thin Vertical Stripe - /// lightVertical - /// - ThinVerticalStripe, - - /// - /// Thin Reverse Diagonal Stripe - /// lightDown - /// - ThinReverseDiagonalStripe, - - /// - /// Thin Diagonal Stripe - /// lightUp - /// - ThinDiagonalStripe, - - /// - /// Thin Horizontal Crosshatch - /// lightGrid - /// - ThinHorizontalCrosshatch, - - /// - /// Thin Diagonal Crosshatch - /// lightTrellis - /// - ThinDiagonalCrosshatch - } -} + /// + /// Horizontal Stripe + /// darkHorizontal + /// + HorizontalStripe, + + /// + /// Vertical Stripe + /// darkVertical + /// + VerticalStripe, + + /// + /// Reverse Diagonal Stripe + /// darkDown + /// + ReverseDiagonalStripe, + + /// + /// Diagonal Stripe + /// darkUp + /// + DiagonalStripe, + + /// + /// Diagonal Crosshatch + /// darkGrid + /// + DiagonalCrosshatch, + + /// + /// Thick Diagonal Crosshatch + /// darkTrellis + /// + ThickDiagonalCrosshatch, + + /// + /// Thin Horizontal Stripe + /// lightHorizontal + /// + ThinHorizontalStripe, + + /// + /// Thin Vertical Stripe + /// lightVertical + /// + ThinVerticalStripe, + + /// + /// Thin Reverse Diagonal Stripe + /// lightDown + /// + ThinReverseDiagonalStripe, + + /// + /// Thin Diagonal Stripe + /// lightUp + /// + ThinDiagonalStripe, + + /// + /// Thin Horizontal Crosshatch + /// lightGrid + /// + ThinHorizontalCrosshatch, + + /// + /// Thin Diagonal Crosshatch + /// lightTrellis + /// + ThinDiagonalCrosshatch +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/VerticalAlign.cs b/src/Simplexcel/Cells/VerticalAlign.cs index d29d7e5..5acd068 100644 --- a/src/Simplexcel/Cells/VerticalAlign.cs +++ b/src/Simplexcel/Cells/VerticalAlign.cs @@ -1,43 +1,42 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// The Vertical Alignment of content within a Cell +/// +public enum VerticalAlign { /// - /// The Vertical Alignment of content within a Cell + /// No specific alignment, use Excel's default /// - public enum VerticalAlign - { - /// - /// No specific alignment, use Excel's default - /// - None = 0, + None = 0, - /// - /// The vertical alignment is aligned-to-top. - /// - Top, + /// + /// The vertical alignment is aligned-to-top. + /// + Top, - /// - /// The vertical alignment is centered across the height of the cell. - /// - Middle, + /// + /// The vertical alignment is centered across the height of the cell. + /// + Middle, - /// - /// The vertical alignment is aligned-to-bottom. - /// - Bottom, + /// + /// The vertical alignment is aligned-to-bottom. + /// + Bottom, - /// - /// When text direction is horizontal: the vertical alignment of lines of text is - /// distributed vertically, where each line of text inside the cell is evenly - /// distributed across the height of the cell, with flush top and bottom margins. - /// - /// When text direction is vertical: similar behavior as horizontal justification. - /// The alignment is justified (flush top and bottom in this case). For each line - /// of text, each line of the wrapped text in a cell is aligned to the top and - /// bottom (except the last line). If no single line of text wraps in the cell, - /// then the text is not justified. - /// - Justify, + /// + /// When text direction is horizontal: the vertical alignment of lines of text is + /// distributed vertically, where each line of text inside the cell is evenly + /// distributed across the height of the cell, with flush top and bottom margins. + /// + /// When text direction is vertical: similar behavior as horizontal justification. + /// The alignment is justified (flush top and bottom in this case). For each line + /// of text, each line of the wrapped text in a cell is aligned to the top and + /// bottom (except the last line). If no single line of text wraps in the cell, + /// then the text is not justified. + /// + Justify, - //Distributed - } -} + //Distributed +} \ No newline at end of file diff --git a/src/Simplexcel/Cells/XlsxColumnAttribute.cs b/src/Simplexcel/Cells/XlsxColumnAttribute.cs index 1214c48..8dd584a 100644 --- a/src/Simplexcel/Cells/XlsxColumnAttribute.cs +++ b/src/Simplexcel/Cells/XlsxColumnAttribute.cs @@ -1,53 +1,52 @@ using System; -namespace Simplexcel +namespace Simplexcel; + +/// +/// Used with or , allows setting how object properties are handled. +/// +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] +public class XlsxColumnAttribute : Attribute { /// - /// Used with or , allows setting how object properties are handled. + /// The name of the Column, used as the Header row. + /// If this is NULL, the Property Name will be used. + /// If this is an Empty string, then no text will be in the Cell header. /// - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public class XlsxColumnAttribute : Attribute - { - /// - /// The name of the Column, used as the Header row. - /// If this is NULL, the Property Name will be used. - /// If this is an Empty string, then no text will be in the Cell header. - /// - public string Name { get; set; } - - /// - /// The Index of the Column, e.g., "0" for "A". - /// If there are Columns with and without an Index, the columns - /// without an Index will be added after the last column with an Index. - /// - /// It is recommended that either all object properties or none specify Column Indexes - /// - public int ColumnIndex { get; set; } + public string Name { get; set; } - /// - /// Create a new XlsxColumnAttribute - /// - public XlsxColumnAttribute() - { - ColumnIndex = -1; - } + /// + /// The Index of the Column, e.g., "0" for "A". + /// If there are Columns with and without an Index, the columns + /// without an Index will be added after the last column with an Index. + /// + /// It is recommended that either all object properties or none specify Column Indexes + /// + public int ColumnIndex { get; set; } - /// - /// Create a new XlsxColumnAttribute with the given name - /// - /// - public XlsxColumnAttribute(string name) - { - Name = name; - ColumnIndex = -1; - } + /// + /// Create a new XlsxColumnAttribute + /// + public XlsxColumnAttribute() + { + ColumnIndex = -1; } /// - /// This attribute causes to ignore the property completely + /// Create a new XlsxColumnAttribute with the given name /// - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public sealed class XlsxIgnoreColumnAttribute : Attribute + /// + public XlsxColumnAttribute(string name) { + Name = name; + ColumnIndex = -1; } } + +/// +/// This attribute causes to ignore the property completely +/// +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] +public sealed class XlsxIgnoreColumnAttribute : Attribute +{ +} \ No newline at end of file diff --git a/src/Simplexcel/Color.cs b/src/Simplexcel/Color.cs index a73dc15..e422aaa 100644 --- a/src/Simplexcel/Color.cs +++ b/src/Simplexcel/Color.cs @@ -1,763 +1,762 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// Specify a Color, as System.Drawing.Color isn't used. +/// +public struct Color { /// - /// Specify a Color, as System.Drawing.Color isn't used. + /// The Red component of the Color + /// + public readonly byte R; + + /// + /// The Green component of the Color + /// + public readonly byte G; + + /// + /// The Blue component of the Color + /// + public readonly byte B; + + /// + /// The Alpha component of the Color (0 = Fully Transparent) + /// + public readonly byte A; + + private Color(byte alpha, byte red, byte green, byte blue) + { + A = alpha; + R = red; + G = green; + B = blue; + } + + /// + /// Create a from the given values + /// + /// + /// + /// + /// + /// + public static Color FromArgb(byte alpha, byte red, byte green, byte blue) + { + return new Color(alpha, red, green, blue); + } + + /// + /// Output the Color as a Hex String in ARGB order, e.g., "FFFF0000" /// - public struct Color + /// + public override string ToString() { - /// - /// The Red component of the Color - /// - public readonly byte R; - - /// - /// The Green component of the Color - /// - public readonly byte G; - - /// - /// The Blue component of the Color - /// - public readonly byte B; - - /// - /// The Alpha component of the Color (0 = Fully Transparent) - /// - public readonly byte A; - - private Color(byte alpha, byte red, byte green, byte blue) - { - A = alpha; - R = red; - G = green; - B = blue; - } - - /// - /// Create a from the given values - /// - /// - /// - /// - /// - /// - public static Color FromArgb(byte alpha, byte red, byte green, byte blue) - { - return new Color(alpha, red, green, blue); - } - - /// - /// Output the Color as a Hex String in ARGB order, e.g., "FFFF0000" - /// - /// - public override string ToString() - { - return $"{A:X2}{R:X2}{G:X2}{B:X2}"; - } - - /// - /// AliceBlue (Hex #FFF0F8FF, ARGB: 255, 240, 248, 255) - /// - public static readonly Color AliceBlue = FromArgb(255, 240, 248, 255); - - /// - /// AntiqueWhite (Hex #FFFAEBD7, ARGB: 255, 250, 235, 215) - /// - public static readonly Color AntiqueWhite = FromArgb(255, 250, 235, 215); - - /// - /// Aqua (Hex #FF00FFFF, ARGB: 255, 0, 255, 255) - /// - public static readonly Color Aqua = FromArgb(255, 0, 255, 255); - - /// - /// Aquamarine (Hex #FF7FFFD4, ARGB: 255, 127, 255, 212) - /// - public static readonly Color Aquamarine = FromArgb(255, 127, 255, 212); - - /// - /// Azure (Hex #FFF0FFFF, ARGB: 255, 240, 255, 255) - /// - public static readonly Color Azure = FromArgb(255, 240, 255, 255); - - /// - /// Beige (Hex #FFF5F5DC, ARGB: 255, 245, 245, 220) - /// - public static readonly Color Beige = FromArgb(255, 245, 245, 220); - - /// - /// Bisque (Hex #FFFFE4C4, ARGB: 255, 255, 228, 196) - /// - public static readonly Color Bisque = FromArgb(255, 255, 228, 196); - - /// - /// Black (Hex #FF000000, ARGB: 255, 0, 0, 0) - /// - public static readonly Color Black = FromArgb(255, 0, 0, 0); - - /// - /// BlanchedAlmond (Hex #FFFFEBCD, ARGB: 255, 255, 235, 205) - /// - public static readonly Color BlanchedAlmond = FromArgb(255, 255, 235, 205); - - /// - /// Blue (Hex #FF0000FF, ARGB: 255, 0, 0, 255) - /// - public static readonly Color Blue = FromArgb(255, 0, 0, 255); - - /// - /// BlueViolet (Hex #FF8A2BE2, ARGB: 255, 138, 43, 226) - /// - public static readonly Color BlueViolet = FromArgb(255, 138, 43, 226); - - /// - /// Brown (Hex #FFA52A2A, ARGB: 255, 165, 42, 42) - /// - public static readonly Color Brown = FromArgb(255, 165, 42, 42); - - /// - /// BurlyWood (Hex #FFDEB887, ARGB: 255, 222, 184, 135) - /// - public static readonly Color BurlyWood = FromArgb(255, 222, 184, 135); - - /// - /// CadetBlue (Hex #FF5F9EA0, ARGB: 255, 95, 158, 160) - /// - public static readonly Color CadetBlue = FromArgb(255, 95, 158, 160); - - /// - /// Chartreuse (Hex #FF7FFF00, ARGB: 255, 127, 255, 0) - /// - public static readonly Color Chartreuse = FromArgb(255, 127, 255, 0); - - /// - /// Chocolate (Hex #FFD2691E, ARGB: 255, 210, 105, 30) - /// - public static readonly Color Chocolate = FromArgb(255, 210, 105, 30); - - /// - /// Coral (Hex #FFFF7F50, ARGB: 255, 255, 127, 80) - /// - public static readonly Color Coral = FromArgb(255, 255, 127, 80); - - /// - /// CornflowerBlue (Hex #FF6495ED, ARGB: 255, 100, 149, 237) - /// - public static readonly Color CornflowerBlue = FromArgb(255, 100, 149, 237); - - /// - /// Cornsilk (Hex #FFFFF8DC, ARGB: 255, 255, 248, 220) - /// - public static readonly Color Cornsilk = FromArgb(255, 255, 248, 220); - - /// - /// Crimson (Hex #FFDC143C, ARGB: 255, 220, 20, 60) - /// - public static readonly Color Crimson = FromArgb(255, 220, 20, 60); - - /// - /// Cyan (Hex #FF00FFFF, ARGB: 255, 0, 255, 255) - /// - public static readonly Color Cyan = FromArgb(255, 0, 255, 255); - - /// - /// DarkBlue (Hex #FF00008B, ARGB: 255, 0, 0, 139) - /// - public static readonly Color DarkBlue = FromArgb(255, 0, 0, 139); - - /// - /// DarkCyan (Hex #FF008B8B, ARGB: 255, 0, 139, 139) - /// - public static readonly Color DarkCyan = FromArgb(255, 0, 139, 139); - - /// - /// DarkGoldenrod (Hex #FFB8860B, ARGB: 255, 184, 134, 11) - /// - public static readonly Color DarkGoldenrod = FromArgb(255, 184, 134, 11); - - /// - /// DarkGray (Hex #FFA9A9A9, ARGB: 255, 169, 169, 169) - /// - public static readonly Color DarkGray = FromArgb(255, 169, 169, 169); - - /// - /// DarkGreen (Hex #FF006400, ARGB: 255, 0, 100, 0) - /// - public static readonly Color DarkGreen = FromArgb(255, 0, 100, 0); - - /// - /// DarkKhaki (Hex #FFBDB76B, ARGB: 255, 189, 183, 107) - /// - public static readonly Color DarkKhaki = FromArgb(255, 189, 183, 107); - - /// - /// DarkMagenta (Hex #FF8B008B, ARGB: 255, 139, 0, 139) - /// - public static readonly Color DarkMagenta = FromArgb(255, 139, 0, 139); - - /// - /// DarkOliveGreen (Hex #FF556B2F, ARGB: 255, 85, 107, 47) - /// - public static readonly Color DarkOliveGreen = FromArgb(255, 85, 107, 47); - - /// - /// DarkOrange (Hex #FFFF8C00, ARGB: 255, 255, 140, 0) - /// - public static readonly Color DarkOrange = FromArgb(255, 255, 140, 0); - - /// - /// DarkOrchid (Hex #FF9932CC, ARGB: 255, 153, 50, 204) - /// - public static readonly Color DarkOrchid = FromArgb(255, 153, 50, 204); - - /// - /// DarkRed (Hex #FF8B0000, ARGB: 255, 139, 0, 0) - /// - public static readonly Color DarkRed = FromArgb(255, 139, 0, 0); - - /// - /// DarkSalmon (Hex #FFE9967A, ARGB: 255, 233, 150, 122) - /// - public static readonly Color DarkSalmon = FromArgb(255, 233, 150, 122); - - /// - /// DarkSeaGreen (Hex #FF8FBC8B, ARGB: 255, 143, 188, 139) - /// - public static readonly Color DarkSeaGreen = FromArgb(255, 143, 188, 139); - - /// - /// DarkSlateBlue (Hex #FF483D8B, ARGB: 255, 72, 61, 139) - /// - public static readonly Color DarkSlateBlue = FromArgb(255, 72, 61, 139); - - /// - /// DarkSlateGray (Hex #FF2F4F4F, ARGB: 255, 47, 79, 79) - /// - public static readonly Color DarkSlateGray = FromArgb(255, 47, 79, 79); - - /// - /// DarkTurquoise (Hex #FF00CED1, ARGB: 255, 0, 206, 209) - /// - public static readonly Color DarkTurquoise = FromArgb(255, 0, 206, 209); - - /// - /// DarkViolet (Hex #FF9400D3, ARGB: 255, 148, 0, 211) - /// - public static readonly Color DarkViolet = FromArgb(255, 148, 0, 211); - - /// - /// DeepPink (Hex #FFFF1493, ARGB: 255, 255, 20, 147) - /// - public static readonly Color DeepPink = FromArgb(255, 255, 20, 147); - - /// - /// DeepSkyBlue (Hex #FF00BFFF, ARGB: 255, 0, 191, 255) - /// - public static readonly Color DeepSkyBlue = FromArgb(255, 0, 191, 255); - - /// - /// DimGray (Hex #FF696969, ARGB: 255, 105, 105, 105) - /// - public static readonly Color DimGray = FromArgb(255, 105, 105, 105); - - /// - /// DodgerBlue (Hex #FF1E90FF, ARGB: 255, 30, 144, 255) - /// - public static readonly Color DodgerBlue = FromArgb(255, 30, 144, 255); - - /// - /// Firebrick (Hex #FFB22222, ARGB: 255, 178, 34, 34) - /// - public static readonly Color Firebrick = FromArgb(255, 178, 34, 34); - - /// - /// FloralWhite (Hex #FFFFFAF0, ARGB: 255, 255, 250, 240) - /// - public static readonly Color FloralWhite = FromArgb(255, 255, 250, 240); - - /// - /// ForestGreen (Hex #FF228B22, ARGB: 255, 34, 139, 34) - /// - public static readonly Color ForestGreen = FromArgb(255, 34, 139, 34); - - /// - /// Fuchsia (Hex #FFFF00FF, ARGB: 255, 255, 0, 255) - /// - public static readonly Color Fuchsia = FromArgb(255, 255, 0, 255); - - /// - /// Gainsboro (Hex #FFDCDCDC, ARGB: 255, 220, 220, 220) - /// - public static readonly Color Gainsboro = FromArgb(255, 220, 220, 220); - - /// - /// GhostWhite (Hex #FFF8F8FF, ARGB: 255, 248, 248, 255) - /// - public static readonly Color GhostWhite = FromArgb(255, 248, 248, 255); - - /// - /// Gold (Hex #FFFFD700, ARGB: 255, 255, 215, 0) - /// - public static readonly Color Gold = FromArgb(255, 255, 215, 0); - - /// - /// Goldenrod (Hex #FFDAA520, ARGB: 255, 218, 165, 32) - /// - public static readonly Color Goldenrod = FromArgb(255, 218, 165, 32); - - /// - /// Gray (Hex #FF808080, ARGB: 255, 128, 128, 128) - /// - public static readonly Color Gray = FromArgb(255, 128, 128, 128); - - /// - /// Green (Hex #FF008000, ARGB: 255, 0, 128, 0) - /// - public static readonly Color Green = FromArgb(255, 0, 128, 0); - - /// - /// GreenYellow (Hex #FFADFF2F, ARGB: 255, 173, 255, 47) - /// - public static readonly Color GreenYellow = FromArgb(255, 173, 255, 47); - - /// - /// Honeydew (Hex #FFF0FFF0, ARGB: 255, 240, 255, 240) - /// - public static readonly Color Honeydew = FromArgb(255, 240, 255, 240); - - /// - /// HotPink (Hex #FFFF69B4, ARGB: 255, 255, 105, 180) - /// - public static readonly Color HotPink = FromArgb(255, 255, 105, 180); - - /// - /// IndianRed (Hex #FFCD5C5C, ARGB: 255, 205, 92, 92) - /// - public static readonly Color IndianRed = FromArgb(255, 205, 92, 92); - - /// - /// Indigo (Hex #FF4B0082, ARGB: 255, 75, 0, 130) - /// - public static readonly Color Indigo = FromArgb(255, 75, 0, 130); - - /// - /// Ivory (Hex #FFFFFFF0, ARGB: 255, 255, 255, 240) - /// - public static readonly Color Ivory = FromArgb(255, 255, 255, 240); - - /// - /// Khaki (Hex #FFF0E68C, ARGB: 255, 240, 230, 140) - /// - public static readonly Color Khaki = FromArgb(255, 240, 230, 140); - - /// - /// Lavender (Hex #FFE6E6FA, ARGB: 255, 230, 230, 250) - /// - public static readonly Color Lavender = FromArgb(255, 230, 230, 250); - - /// - /// LavenderBlush (Hex #FFFFF0F5, ARGB: 255, 255, 240, 245) - /// - public static readonly Color LavenderBlush = FromArgb(255, 255, 240, 245); - - /// - /// LawnGreen (Hex #FF7CFC00, ARGB: 255, 124, 252, 0) - /// - public static readonly Color LawnGreen = FromArgb(255, 124, 252, 0); - - /// - /// LemonChiffon (Hex #FFFFFACD, ARGB: 255, 255, 250, 205) - /// - public static readonly Color LemonChiffon = FromArgb(255, 255, 250, 205); - - /// - /// LightBlue (Hex #FFADD8E6, ARGB: 255, 173, 216, 230) - /// - public static readonly Color LightBlue = FromArgb(255, 173, 216, 230); - - /// - /// LightCoral (Hex #FFF08080, ARGB: 255, 240, 128, 128) - /// - public static readonly Color LightCoral = FromArgb(255, 240, 128, 128); - - /// - /// LightCyan (Hex #FFE0FFFF, ARGB: 255, 224, 255, 255) - /// - public static readonly Color LightCyan = FromArgb(255, 224, 255, 255); - - /// - /// LightGoldenrodYellow (Hex #FFFAFAD2, ARGB: 255, 250, 250, 210) - /// - public static readonly Color LightGoldenrodYellow = FromArgb(255, 250, 250, 210); - - /// - /// LightGray (Hex #FFD3D3D3, ARGB: 255, 211, 211, 211) - /// - public static readonly Color LightGray = FromArgb(255, 211, 211, 211); - - /// - /// LightGreen (Hex #FF90EE90, ARGB: 255, 144, 238, 144) - /// - public static readonly Color LightGreen = FromArgb(255, 144, 238, 144); - - /// - /// LightPink (Hex #FFFFB6C1, ARGB: 255, 255, 182, 193) - /// - public static readonly Color LightPink = FromArgb(255, 255, 182, 193); - - /// - /// LightSalmon (Hex #FFFFA07A, ARGB: 255, 255, 160, 122) - /// - public static readonly Color LightSalmon = FromArgb(255, 255, 160, 122); - - /// - /// LightSeaGreen (Hex #FF20B2AA, ARGB: 255, 32, 178, 170) - /// - public static readonly Color LightSeaGreen = FromArgb(255, 32, 178, 170); - - /// - /// LightSkyBlue (Hex #FF87CEFA, ARGB: 255, 135, 206, 250) - /// - public static readonly Color LightSkyBlue = FromArgb(255, 135, 206, 250); - - /// - /// LightSlateGray (Hex #FF778899, ARGB: 255, 119, 136, 153) - /// - public static readonly Color LightSlateGray = FromArgb(255, 119, 136, 153); - - /// - /// LightSteelBlue (Hex #FFB0C4DE, ARGB: 255, 176, 196, 222) - /// - public static readonly Color LightSteelBlue = FromArgb(255, 176, 196, 222); - - /// - /// LightYellow (Hex #FFFFFFE0, ARGB: 255, 255, 255, 224) - /// - public static readonly Color LightYellow = FromArgb(255, 255, 255, 224); - - /// - /// Lime (Hex #FF00FF00, ARGB: 255, 0, 255, 0) - /// - public static readonly Color Lime = FromArgb(255, 0, 255, 0); - - /// - /// LimeGreen (Hex #FF32CD32, ARGB: 255, 50, 205, 50) - /// - public static readonly Color LimeGreen = FromArgb(255, 50, 205, 50); - - /// - /// Linen (Hex #FFFAF0E6, ARGB: 255, 250, 240, 230) - /// - public static readonly Color Linen = FromArgb(255, 250, 240, 230); - - /// - /// Magenta (Hex #FFFF00FF, ARGB: 255, 255, 0, 255) - /// - public static readonly Color Magenta = FromArgb(255, 255, 0, 255); - - /// - /// Maroon (Hex #FF800000, ARGB: 255, 128, 0, 0) - /// - public static readonly Color Maroon = FromArgb(255, 128, 0, 0); - - /// - /// MediumAquamarine (Hex #FF66CDAA, ARGB: 255, 102, 205, 170) - /// - public static readonly Color MediumAquamarine = FromArgb(255, 102, 205, 170); - - /// - /// MediumBlue (Hex #FF0000CD, ARGB: 255, 0, 0, 205) - /// - public static readonly Color MediumBlue = FromArgb(255, 0, 0, 205); - - /// - /// MediumOrchid (Hex #FFBA55D3, ARGB: 255, 186, 85, 211) - /// - public static readonly Color MediumOrchid = FromArgb(255, 186, 85, 211); - - /// - /// MediumPurple (Hex #FF9370DB, ARGB: 255, 147, 112, 219) - /// - public static readonly Color MediumPurple = FromArgb(255, 147, 112, 219); - - /// - /// MediumSeaGreen (Hex #FF3CB371, ARGB: 255, 60, 179, 113) - /// - public static readonly Color MediumSeaGreen = FromArgb(255, 60, 179, 113); - - /// - /// MediumSlateBlue (Hex #FF7B68EE, ARGB: 255, 123, 104, 238) - /// - public static readonly Color MediumSlateBlue = FromArgb(255, 123, 104, 238); - - /// - /// MediumSpringGreen (Hex #FF00FA9A, ARGB: 255, 0, 250, 154) - /// - public static readonly Color MediumSpringGreen = FromArgb(255, 0, 250, 154); - - /// - /// MediumTurquoise (Hex #FF48D1CC, ARGB: 255, 72, 209, 204) - /// - public static readonly Color MediumTurquoise = FromArgb(255, 72, 209, 204); - - /// - /// MediumVioletRed (Hex #FFC71585, ARGB: 255, 199, 21, 133) - /// - public static readonly Color MediumVioletRed = FromArgb(255, 199, 21, 133); - - /// - /// MidnightBlue (Hex #FF191970, ARGB: 255, 25, 25, 112) - /// - public static readonly Color MidnightBlue = FromArgb(255, 25, 25, 112); - - /// - /// MintCream (Hex #FFF5FFFA, ARGB: 255, 245, 255, 250) - /// - public static readonly Color MintCream = FromArgb(255, 245, 255, 250); - - /// - /// MistyRose (Hex #FFFFE4E1, ARGB: 255, 255, 228, 225) - /// - public static readonly Color MistyRose = FromArgb(255, 255, 228, 225); - - /// - /// Moccasin (Hex #FFFFE4B5, ARGB: 255, 255, 228, 181) - /// - public static readonly Color Moccasin = FromArgb(255, 255, 228, 181); - - /// - /// NavajoWhite (Hex #FFFFDEAD, ARGB: 255, 255, 222, 173) - /// - public static readonly Color NavajoWhite = FromArgb(255, 255, 222, 173); - - /// - /// Navy (Hex #FF000080, ARGB: 255, 0, 0, 128) - /// - public static readonly Color Navy = FromArgb(255, 0, 0, 128); - - /// - /// OldLace (Hex #FFFDF5E6, ARGB: 255, 253, 245, 230) - /// - public static readonly Color OldLace = FromArgb(255, 253, 245, 230); - - /// - /// Olive (Hex #FF808000, ARGB: 255, 128, 128, 0) - /// - public static readonly Color Olive = FromArgb(255, 128, 128, 0); - - /// - /// OliveDrab (Hex #FF6B8E23, ARGB: 255, 107, 142, 35) - /// - public static readonly Color OliveDrab = FromArgb(255, 107, 142, 35); - - /// - /// Orange (Hex #FFFFA500, ARGB: 255, 255, 165, 0) - /// - public static readonly Color Orange = FromArgb(255, 255, 165, 0); - - /// - /// OrangeRed (Hex #FFFF4500, ARGB: 255, 255, 69, 0) - /// - public static readonly Color OrangeRed = FromArgb(255, 255, 69, 0); - - /// - /// Orchid (Hex #FFDA70D6, ARGB: 255, 218, 112, 214) - /// - public static readonly Color Orchid = FromArgb(255, 218, 112, 214); - - /// - /// PaleGoldenrod (Hex #FFEEE8AA, ARGB: 255, 238, 232, 170) - /// - public static readonly Color PaleGoldenrod = FromArgb(255, 238, 232, 170); - - /// - /// PaleGreen (Hex #FF98FB98, ARGB: 255, 152, 251, 152) - /// - public static readonly Color PaleGreen = FromArgb(255, 152, 251, 152); - - /// - /// PaleTurquoise (Hex #FFAFEEEE, ARGB: 255, 175, 238, 238) - /// - public static readonly Color PaleTurquoise = FromArgb(255, 175, 238, 238); - - /// - /// PaleVioletRed (Hex #FFDB7093, ARGB: 255, 219, 112, 147) - /// - public static readonly Color PaleVioletRed = FromArgb(255, 219, 112, 147); - - /// - /// PapayaWhip (Hex #FFFFEFD5, ARGB: 255, 255, 239, 213) - /// - public static readonly Color PapayaWhip = FromArgb(255, 255, 239, 213); - - /// - /// PeachPuff (Hex #FFFFDAB9, ARGB: 255, 255, 218, 185) - /// - public static readonly Color PeachPuff = FromArgb(255, 255, 218, 185); - - /// - /// Peru (Hex #FFCD853F, ARGB: 255, 205, 133, 63) - /// - public static readonly Color Peru = FromArgb(255, 205, 133, 63); - - /// - /// Pink (Hex #FFFFC0CB, ARGB: 255, 255, 192, 203) - /// - public static readonly Color Pink = FromArgb(255, 255, 192, 203); - - /// - /// Plum (Hex #FFDDA0DD, ARGB: 255, 221, 160, 221) - /// - public static readonly Color Plum = FromArgb(255, 221, 160, 221); - - /// - /// PowderBlue (Hex #FFB0E0E6, ARGB: 255, 176, 224, 230) - /// - public static readonly Color PowderBlue = FromArgb(255, 176, 224, 230); - - /// - /// Purple (Hex #FF800080, ARGB: 255, 128, 0, 128) - /// - public static readonly Color Purple = FromArgb(255, 128, 0, 128); - - /// - /// Red (Hex #FFFF0000, ARGB: 255, 255, 0, 0) - /// - public static readonly Color Red = FromArgb(255, 255, 0, 0); - - /// - /// RosyBrown (Hex #FFBC8F8F, ARGB: 255, 188, 143, 143) - /// - public static readonly Color RosyBrown = FromArgb(255, 188, 143, 143); - - /// - /// RoyalBlue (Hex #FF4169E1, ARGB: 255, 65, 105, 225) - /// - public static readonly Color RoyalBlue = FromArgb(255, 65, 105, 225); - - /// - /// SaddleBrown (Hex #FF8B4513, ARGB: 255, 139, 69, 19) - /// - public static readonly Color SaddleBrown = FromArgb(255, 139, 69, 19); - - /// - /// Salmon (Hex #FFFA8072, ARGB: 255, 250, 128, 114) - /// - public static readonly Color Salmon = FromArgb(255, 250, 128, 114); - - /// - /// SandyBrown (Hex #FFF4A460, ARGB: 255, 244, 164, 96) - /// - public static readonly Color SandyBrown = FromArgb(255, 244, 164, 96); - - /// - /// SeaGreen (Hex #FF2E8B57, ARGB: 255, 46, 139, 87) - /// - public static readonly Color SeaGreen = FromArgb(255, 46, 139, 87); - - /// - /// SeaShell (Hex #FFFFF5EE, ARGB: 255, 255, 245, 238) - /// - public static readonly Color SeaShell = FromArgb(255, 255, 245, 238); - - /// - /// Sienna (Hex #FFA0522D, ARGB: 255, 160, 82, 45) - /// - public static readonly Color Sienna = FromArgb(255, 160, 82, 45); - - /// - /// Silver (Hex #FFC0C0C0, ARGB: 255, 192, 192, 192) - /// - public static readonly Color Silver = FromArgb(255, 192, 192, 192); - - /// - /// SkyBlue (Hex #FF87CEEB, ARGB: 255, 135, 206, 235) - /// - public static readonly Color SkyBlue = FromArgb(255, 135, 206, 235); - - /// - /// SlateBlue (Hex #FF6A5ACD, ARGB: 255, 106, 90, 205) - /// - public static readonly Color SlateBlue = FromArgb(255, 106, 90, 205); - - /// - /// SlateGray (Hex #FF708090, ARGB: 255, 112, 128, 144) - /// - public static readonly Color SlateGray = FromArgb(255, 112, 128, 144); - - /// - /// Snow (Hex #FFFFFAFA, ARGB: 255, 255, 250, 250) - /// - public static readonly Color Snow = FromArgb(255, 255, 250, 250); - - /// - /// SpringGreen (Hex #FF00FF7F, ARGB: 255, 0, 255, 127) - /// - public static readonly Color SpringGreen = FromArgb(255, 0, 255, 127); - - /// - /// SteelBlue (Hex #FF4682B4, ARGB: 255, 70, 130, 180) - /// - public static readonly Color SteelBlue = FromArgb(255, 70, 130, 180); - - /// - /// Tan (Hex #FFD2B48C, ARGB: 255, 210, 180, 140) - /// - public static readonly Color Tan = FromArgb(255, 210, 180, 140); - - /// - /// Teal (Hex #FF008080, ARGB: 255, 0, 128, 128) - /// - public static readonly Color Teal = FromArgb(255, 0, 128, 128); - - /// - /// Thistle (Hex #FFD8BFD8, ARGB: 255, 216, 191, 216) - /// - public static readonly Color Thistle = FromArgb(255, 216, 191, 216); - - /// - /// Tomato (Hex #FFFF6347, ARGB: 255, 255, 99, 71) - /// - public static readonly Color Tomato = FromArgb(255, 255, 99, 71); - - /// - /// Transparent (Hex #00FFFFFF, ARGB: 0, 255, 255, 255) - /// - public static readonly Color Transparent = FromArgb(0, 255, 255, 255); - - /// - /// Turquoise (Hex #FF40E0D0, ARGB: 255, 64, 224, 208) - /// - public static readonly Color Turquoise = FromArgb(255, 64, 224, 208); - - /// - /// Violet (Hex #FFEE82EE, ARGB: 255, 238, 130, 238) - /// - public static readonly Color Violet = FromArgb(255, 238, 130, 238); - - /// - /// Wheat (Hex #FFF5DEB3, ARGB: 255, 245, 222, 179) - /// - public static readonly Color Wheat = FromArgb(255, 245, 222, 179); - - /// - /// White (Hex #FFFFFFFF, ARGB: 255, 255, 255, 255) - /// - public static readonly Color White = FromArgb(255, 255, 255, 255); - - /// - /// WhiteSmoke (Hex #FFF5F5F5, ARGB: 255, 245, 245, 245) - /// - public static readonly Color WhiteSmoke = FromArgb(255, 245, 245, 245); - - /// - /// Yellow (Hex #FFFFFF00, ARGB: 255, 255, 255, 0) - /// - public static readonly Color Yellow = FromArgb(255, 255, 255, 0); - - /// - /// YellowGreen (Hex #FF9ACD32, ARGB: 255, 154, 205, 50) - /// - public static readonly Color YellowGreen = FromArgb(255, 154, 205, 50); + return $"{A:X2}{R:X2}{G:X2}{B:X2}"; } + + /// + /// AliceBlue (Hex #FFF0F8FF, ARGB: 255, 240, 248, 255) + /// + public static readonly Color AliceBlue = FromArgb(255, 240, 248, 255); + + /// + /// AntiqueWhite (Hex #FFFAEBD7, ARGB: 255, 250, 235, 215) + /// + public static readonly Color AntiqueWhite = FromArgb(255, 250, 235, 215); + + /// + /// Aqua (Hex #FF00FFFF, ARGB: 255, 0, 255, 255) + /// + public static readonly Color Aqua = FromArgb(255, 0, 255, 255); + + /// + /// Aquamarine (Hex #FF7FFFD4, ARGB: 255, 127, 255, 212) + /// + public static readonly Color Aquamarine = FromArgb(255, 127, 255, 212); + + /// + /// Azure (Hex #FFF0FFFF, ARGB: 255, 240, 255, 255) + /// + public static readonly Color Azure = FromArgb(255, 240, 255, 255); + + /// + /// Beige (Hex #FFF5F5DC, ARGB: 255, 245, 245, 220) + /// + public static readonly Color Beige = FromArgb(255, 245, 245, 220); + + /// + /// Bisque (Hex #FFFFE4C4, ARGB: 255, 255, 228, 196) + /// + public static readonly Color Bisque = FromArgb(255, 255, 228, 196); + + /// + /// Black (Hex #FF000000, ARGB: 255, 0, 0, 0) + /// + public static readonly Color Black = FromArgb(255, 0, 0, 0); + + /// + /// BlanchedAlmond (Hex #FFFFEBCD, ARGB: 255, 255, 235, 205) + /// + public static readonly Color BlanchedAlmond = FromArgb(255, 255, 235, 205); + + /// + /// Blue (Hex #FF0000FF, ARGB: 255, 0, 0, 255) + /// + public static readonly Color Blue = FromArgb(255, 0, 0, 255); + + /// + /// BlueViolet (Hex #FF8A2BE2, ARGB: 255, 138, 43, 226) + /// + public static readonly Color BlueViolet = FromArgb(255, 138, 43, 226); + + /// + /// Brown (Hex #FFA52A2A, ARGB: 255, 165, 42, 42) + /// + public static readonly Color Brown = FromArgb(255, 165, 42, 42); + + /// + /// BurlyWood (Hex #FFDEB887, ARGB: 255, 222, 184, 135) + /// + public static readonly Color BurlyWood = FromArgb(255, 222, 184, 135); + + /// + /// CadetBlue (Hex #FF5F9EA0, ARGB: 255, 95, 158, 160) + /// + public static readonly Color CadetBlue = FromArgb(255, 95, 158, 160); + + /// + /// Chartreuse (Hex #FF7FFF00, ARGB: 255, 127, 255, 0) + /// + public static readonly Color Chartreuse = FromArgb(255, 127, 255, 0); + + /// + /// Chocolate (Hex #FFD2691E, ARGB: 255, 210, 105, 30) + /// + public static readonly Color Chocolate = FromArgb(255, 210, 105, 30); + + /// + /// Coral (Hex #FFFF7F50, ARGB: 255, 255, 127, 80) + /// + public static readonly Color Coral = FromArgb(255, 255, 127, 80); + + /// + /// CornflowerBlue (Hex #FF6495ED, ARGB: 255, 100, 149, 237) + /// + public static readonly Color CornflowerBlue = FromArgb(255, 100, 149, 237); + + /// + /// Cornsilk (Hex #FFFFF8DC, ARGB: 255, 255, 248, 220) + /// + public static readonly Color Cornsilk = FromArgb(255, 255, 248, 220); + + /// + /// Crimson (Hex #FFDC143C, ARGB: 255, 220, 20, 60) + /// + public static readonly Color Crimson = FromArgb(255, 220, 20, 60); + + /// + /// Cyan (Hex #FF00FFFF, ARGB: 255, 0, 255, 255) + /// + public static readonly Color Cyan = FromArgb(255, 0, 255, 255); + + /// + /// DarkBlue (Hex #FF00008B, ARGB: 255, 0, 0, 139) + /// + public static readonly Color DarkBlue = FromArgb(255, 0, 0, 139); + + /// + /// DarkCyan (Hex #FF008B8B, ARGB: 255, 0, 139, 139) + /// + public static readonly Color DarkCyan = FromArgb(255, 0, 139, 139); + + /// + /// DarkGoldenrod (Hex #FFB8860B, ARGB: 255, 184, 134, 11) + /// + public static readonly Color DarkGoldenrod = FromArgb(255, 184, 134, 11); + + /// + /// DarkGray (Hex #FFA9A9A9, ARGB: 255, 169, 169, 169) + /// + public static readonly Color DarkGray = FromArgb(255, 169, 169, 169); + + /// + /// DarkGreen (Hex #FF006400, ARGB: 255, 0, 100, 0) + /// + public static readonly Color DarkGreen = FromArgb(255, 0, 100, 0); + + /// + /// DarkKhaki (Hex #FFBDB76B, ARGB: 255, 189, 183, 107) + /// + public static readonly Color DarkKhaki = FromArgb(255, 189, 183, 107); + + /// + /// DarkMagenta (Hex #FF8B008B, ARGB: 255, 139, 0, 139) + /// + public static readonly Color DarkMagenta = FromArgb(255, 139, 0, 139); + + /// + /// DarkOliveGreen (Hex #FF556B2F, ARGB: 255, 85, 107, 47) + /// + public static readonly Color DarkOliveGreen = FromArgb(255, 85, 107, 47); + + /// + /// DarkOrange (Hex #FFFF8C00, ARGB: 255, 255, 140, 0) + /// + public static readonly Color DarkOrange = FromArgb(255, 255, 140, 0); + + /// + /// DarkOrchid (Hex #FF9932CC, ARGB: 255, 153, 50, 204) + /// + public static readonly Color DarkOrchid = FromArgb(255, 153, 50, 204); + + /// + /// DarkRed (Hex #FF8B0000, ARGB: 255, 139, 0, 0) + /// + public static readonly Color DarkRed = FromArgb(255, 139, 0, 0); + + /// + /// DarkSalmon (Hex #FFE9967A, ARGB: 255, 233, 150, 122) + /// + public static readonly Color DarkSalmon = FromArgb(255, 233, 150, 122); + + /// + /// DarkSeaGreen (Hex #FF8FBC8B, ARGB: 255, 143, 188, 139) + /// + public static readonly Color DarkSeaGreen = FromArgb(255, 143, 188, 139); + + /// + /// DarkSlateBlue (Hex #FF483D8B, ARGB: 255, 72, 61, 139) + /// + public static readonly Color DarkSlateBlue = FromArgb(255, 72, 61, 139); + + /// + /// DarkSlateGray (Hex #FF2F4F4F, ARGB: 255, 47, 79, 79) + /// + public static readonly Color DarkSlateGray = FromArgb(255, 47, 79, 79); + + /// + /// DarkTurquoise (Hex #FF00CED1, ARGB: 255, 0, 206, 209) + /// + public static readonly Color DarkTurquoise = FromArgb(255, 0, 206, 209); + + /// + /// DarkViolet (Hex #FF9400D3, ARGB: 255, 148, 0, 211) + /// + public static readonly Color DarkViolet = FromArgb(255, 148, 0, 211); + + /// + /// DeepPink (Hex #FFFF1493, ARGB: 255, 255, 20, 147) + /// + public static readonly Color DeepPink = FromArgb(255, 255, 20, 147); + + /// + /// DeepSkyBlue (Hex #FF00BFFF, ARGB: 255, 0, 191, 255) + /// + public static readonly Color DeepSkyBlue = FromArgb(255, 0, 191, 255); + + /// + /// DimGray (Hex #FF696969, ARGB: 255, 105, 105, 105) + /// + public static readonly Color DimGray = FromArgb(255, 105, 105, 105); + + /// + /// DodgerBlue (Hex #FF1E90FF, ARGB: 255, 30, 144, 255) + /// + public static readonly Color DodgerBlue = FromArgb(255, 30, 144, 255); + + /// + /// Firebrick (Hex #FFB22222, ARGB: 255, 178, 34, 34) + /// + public static readonly Color Firebrick = FromArgb(255, 178, 34, 34); + + /// + /// FloralWhite (Hex #FFFFFAF0, ARGB: 255, 255, 250, 240) + /// + public static readonly Color FloralWhite = FromArgb(255, 255, 250, 240); + + /// + /// ForestGreen (Hex #FF228B22, ARGB: 255, 34, 139, 34) + /// + public static readonly Color ForestGreen = FromArgb(255, 34, 139, 34); + + /// + /// Fuchsia (Hex #FFFF00FF, ARGB: 255, 255, 0, 255) + /// + public static readonly Color Fuchsia = FromArgb(255, 255, 0, 255); + + /// + /// Gainsboro (Hex #FFDCDCDC, ARGB: 255, 220, 220, 220) + /// + public static readonly Color Gainsboro = FromArgb(255, 220, 220, 220); + + /// + /// GhostWhite (Hex #FFF8F8FF, ARGB: 255, 248, 248, 255) + /// + public static readonly Color GhostWhite = FromArgb(255, 248, 248, 255); + + /// + /// Gold (Hex #FFFFD700, ARGB: 255, 255, 215, 0) + /// + public static readonly Color Gold = FromArgb(255, 255, 215, 0); + + /// + /// Goldenrod (Hex #FFDAA520, ARGB: 255, 218, 165, 32) + /// + public static readonly Color Goldenrod = FromArgb(255, 218, 165, 32); + + /// + /// Gray (Hex #FF808080, ARGB: 255, 128, 128, 128) + /// + public static readonly Color Gray = FromArgb(255, 128, 128, 128); + + /// + /// Green (Hex #FF008000, ARGB: 255, 0, 128, 0) + /// + public static readonly Color Green = FromArgb(255, 0, 128, 0); + + /// + /// GreenYellow (Hex #FFADFF2F, ARGB: 255, 173, 255, 47) + /// + public static readonly Color GreenYellow = FromArgb(255, 173, 255, 47); + + /// + /// Honeydew (Hex #FFF0FFF0, ARGB: 255, 240, 255, 240) + /// + public static readonly Color Honeydew = FromArgb(255, 240, 255, 240); + + /// + /// HotPink (Hex #FFFF69B4, ARGB: 255, 255, 105, 180) + /// + public static readonly Color HotPink = FromArgb(255, 255, 105, 180); + + /// + /// IndianRed (Hex #FFCD5C5C, ARGB: 255, 205, 92, 92) + /// + public static readonly Color IndianRed = FromArgb(255, 205, 92, 92); + + /// + /// Indigo (Hex #FF4B0082, ARGB: 255, 75, 0, 130) + /// + public static readonly Color Indigo = FromArgb(255, 75, 0, 130); + + /// + /// Ivory (Hex #FFFFFFF0, ARGB: 255, 255, 255, 240) + /// + public static readonly Color Ivory = FromArgb(255, 255, 255, 240); + + /// + /// Khaki (Hex #FFF0E68C, ARGB: 255, 240, 230, 140) + /// + public static readonly Color Khaki = FromArgb(255, 240, 230, 140); + + /// + /// Lavender (Hex #FFE6E6FA, ARGB: 255, 230, 230, 250) + /// + public static readonly Color Lavender = FromArgb(255, 230, 230, 250); + + /// + /// LavenderBlush (Hex #FFFFF0F5, ARGB: 255, 255, 240, 245) + /// + public static readonly Color LavenderBlush = FromArgb(255, 255, 240, 245); + + /// + /// LawnGreen (Hex #FF7CFC00, ARGB: 255, 124, 252, 0) + /// + public static readonly Color LawnGreen = FromArgb(255, 124, 252, 0); + + /// + /// LemonChiffon (Hex #FFFFFACD, ARGB: 255, 255, 250, 205) + /// + public static readonly Color LemonChiffon = FromArgb(255, 255, 250, 205); + + /// + /// LightBlue (Hex #FFADD8E6, ARGB: 255, 173, 216, 230) + /// + public static readonly Color LightBlue = FromArgb(255, 173, 216, 230); + + /// + /// LightCoral (Hex #FFF08080, ARGB: 255, 240, 128, 128) + /// + public static readonly Color LightCoral = FromArgb(255, 240, 128, 128); + + /// + /// LightCyan (Hex #FFE0FFFF, ARGB: 255, 224, 255, 255) + /// + public static readonly Color LightCyan = FromArgb(255, 224, 255, 255); + + /// + /// LightGoldenrodYellow (Hex #FFFAFAD2, ARGB: 255, 250, 250, 210) + /// + public static readonly Color LightGoldenrodYellow = FromArgb(255, 250, 250, 210); + + /// + /// LightGray (Hex #FFD3D3D3, ARGB: 255, 211, 211, 211) + /// + public static readonly Color LightGray = FromArgb(255, 211, 211, 211); + + /// + /// LightGreen (Hex #FF90EE90, ARGB: 255, 144, 238, 144) + /// + public static readonly Color LightGreen = FromArgb(255, 144, 238, 144); + + /// + /// LightPink (Hex #FFFFB6C1, ARGB: 255, 255, 182, 193) + /// + public static readonly Color LightPink = FromArgb(255, 255, 182, 193); + + /// + /// LightSalmon (Hex #FFFFA07A, ARGB: 255, 255, 160, 122) + /// + public static readonly Color LightSalmon = FromArgb(255, 255, 160, 122); + + /// + /// LightSeaGreen (Hex #FF20B2AA, ARGB: 255, 32, 178, 170) + /// + public static readonly Color LightSeaGreen = FromArgb(255, 32, 178, 170); + + /// + /// LightSkyBlue (Hex #FF87CEFA, ARGB: 255, 135, 206, 250) + /// + public static readonly Color LightSkyBlue = FromArgb(255, 135, 206, 250); + + /// + /// LightSlateGray (Hex #FF778899, ARGB: 255, 119, 136, 153) + /// + public static readonly Color LightSlateGray = FromArgb(255, 119, 136, 153); + + /// + /// LightSteelBlue (Hex #FFB0C4DE, ARGB: 255, 176, 196, 222) + /// + public static readonly Color LightSteelBlue = FromArgb(255, 176, 196, 222); + + /// + /// LightYellow (Hex #FFFFFFE0, ARGB: 255, 255, 255, 224) + /// + public static readonly Color LightYellow = FromArgb(255, 255, 255, 224); + + /// + /// Lime (Hex #FF00FF00, ARGB: 255, 0, 255, 0) + /// + public static readonly Color Lime = FromArgb(255, 0, 255, 0); + + /// + /// LimeGreen (Hex #FF32CD32, ARGB: 255, 50, 205, 50) + /// + public static readonly Color LimeGreen = FromArgb(255, 50, 205, 50); + + /// + /// Linen (Hex #FFFAF0E6, ARGB: 255, 250, 240, 230) + /// + public static readonly Color Linen = FromArgb(255, 250, 240, 230); + + /// + /// Magenta (Hex #FFFF00FF, ARGB: 255, 255, 0, 255) + /// + public static readonly Color Magenta = FromArgb(255, 255, 0, 255); + + /// + /// Maroon (Hex #FF800000, ARGB: 255, 128, 0, 0) + /// + public static readonly Color Maroon = FromArgb(255, 128, 0, 0); + + /// + /// MediumAquamarine (Hex #FF66CDAA, ARGB: 255, 102, 205, 170) + /// + public static readonly Color MediumAquamarine = FromArgb(255, 102, 205, 170); + + /// + /// MediumBlue (Hex #FF0000CD, ARGB: 255, 0, 0, 205) + /// + public static readonly Color MediumBlue = FromArgb(255, 0, 0, 205); + + /// + /// MediumOrchid (Hex #FFBA55D3, ARGB: 255, 186, 85, 211) + /// + public static readonly Color MediumOrchid = FromArgb(255, 186, 85, 211); + + /// + /// MediumPurple (Hex #FF9370DB, ARGB: 255, 147, 112, 219) + /// + public static readonly Color MediumPurple = FromArgb(255, 147, 112, 219); + + /// + /// MediumSeaGreen (Hex #FF3CB371, ARGB: 255, 60, 179, 113) + /// + public static readonly Color MediumSeaGreen = FromArgb(255, 60, 179, 113); + + /// + /// MediumSlateBlue (Hex #FF7B68EE, ARGB: 255, 123, 104, 238) + /// + public static readonly Color MediumSlateBlue = FromArgb(255, 123, 104, 238); + + /// + /// MediumSpringGreen (Hex #FF00FA9A, ARGB: 255, 0, 250, 154) + /// + public static readonly Color MediumSpringGreen = FromArgb(255, 0, 250, 154); + + /// + /// MediumTurquoise (Hex #FF48D1CC, ARGB: 255, 72, 209, 204) + /// + public static readonly Color MediumTurquoise = FromArgb(255, 72, 209, 204); + + /// + /// MediumVioletRed (Hex #FFC71585, ARGB: 255, 199, 21, 133) + /// + public static readonly Color MediumVioletRed = FromArgb(255, 199, 21, 133); + + /// + /// MidnightBlue (Hex #FF191970, ARGB: 255, 25, 25, 112) + /// + public static readonly Color MidnightBlue = FromArgb(255, 25, 25, 112); + + /// + /// MintCream (Hex #FFF5FFFA, ARGB: 255, 245, 255, 250) + /// + public static readonly Color MintCream = FromArgb(255, 245, 255, 250); + + /// + /// MistyRose (Hex #FFFFE4E1, ARGB: 255, 255, 228, 225) + /// + public static readonly Color MistyRose = FromArgb(255, 255, 228, 225); + + /// + /// Moccasin (Hex #FFFFE4B5, ARGB: 255, 255, 228, 181) + /// + public static readonly Color Moccasin = FromArgb(255, 255, 228, 181); + + /// + /// NavajoWhite (Hex #FFFFDEAD, ARGB: 255, 255, 222, 173) + /// + public static readonly Color NavajoWhite = FromArgb(255, 255, 222, 173); + + /// + /// Navy (Hex #FF000080, ARGB: 255, 0, 0, 128) + /// + public static readonly Color Navy = FromArgb(255, 0, 0, 128); + + /// + /// OldLace (Hex #FFFDF5E6, ARGB: 255, 253, 245, 230) + /// + public static readonly Color OldLace = FromArgb(255, 253, 245, 230); + + /// + /// Olive (Hex #FF808000, ARGB: 255, 128, 128, 0) + /// + public static readonly Color Olive = FromArgb(255, 128, 128, 0); + + /// + /// OliveDrab (Hex #FF6B8E23, ARGB: 255, 107, 142, 35) + /// + public static readonly Color OliveDrab = FromArgb(255, 107, 142, 35); + + /// + /// Orange (Hex #FFFFA500, ARGB: 255, 255, 165, 0) + /// + public static readonly Color Orange = FromArgb(255, 255, 165, 0); + + /// + /// OrangeRed (Hex #FFFF4500, ARGB: 255, 255, 69, 0) + /// + public static readonly Color OrangeRed = FromArgb(255, 255, 69, 0); + + /// + /// Orchid (Hex #FFDA70D6, ARGB: 255, 218, 112, 214) + /// + public static readonly Color Orchid = FromArgb(255, 218, 112, 214); + + /// + /// PaleGoldenrod (Hex #FFEEE8AA, ARGB: 255, 238, 232, 170) + /// + public static readonly Color PaleGoldenrod = FromArgb(255, 238, 232, 170); + + /// + /// PaleGreen (Hex #FF98FB98, ARGB: 255, 152, 251, 152) + /// + public static readonly Color PaleGreen = FromArgb(255, 152, 251, 152); + + /// + /// PaleTurquoise (Hex #FFAFEEEE, ARGB: 255, 175, 238, 238) + /// + public static readonly Color PaleTurquoise = FromArgb(255, 175, 238, 238); + + /// + /// PaleVioletRed (Hex #FFDB7093, ARGB: 255, 219, 112, 147) + /// + public static readonly Color PaleVioletRed = FromArgb(255, 219, 112, 147); + + /// + /// PapayaWhip (Hex #FFFFEFD5, ARGB: 255, 255, 239, 213) + /// + public static readonly Color PapayaWhip = FromArgb(255, 255, 239, 213); + + /// + /// PeachPuff (Hex #FFFFDAB9, ARGB: 255, 255, 218, 185) + /// + public static readonly Color PeachPuff = FromArgb(255, 255, 218, 185); + + /// + /// Peru (Hex #FFCD853F, ARGB: 255, 205, 133, 63) + /// + public static readonly Color Peru = FromArgb(255, 205, 133, 63); + + /// + /// Pink (Hex #FFFFC0CB, ARGB: 255, 255, 192, 203) + /// + public static readonly Color Pink = FromArgb(255, 255, 192, 203); + + /// + /// Plum (Hex #FFDDA0DD, ARGB: 255, 221, 160, 221) + /// + public static readonly Color Plum = FromArgb(255, 221, 160, 221); + + /// + /// PowderBlue (Hex #FFB0E0E6, ARGB: 255, 176, 224, 230) + /// + public static readonly Color PowderBlue = FromArgb(255, 176, 224, 230); + + /// + /// Purple (Hex #FF800080, ARGB: 255, 128, 0, 128) + /// + public static readonly Color Purple = FromArgb(255, 128, 0, 128); + + /// + /// Red (Hex #FFFF0000, ARGB: 255, 255, 0, 0) + /// + public static readonly Color Red = FromArgb(255, 255, 0, 0); + + /// + /// RosyBrown (Hex #FFBC8F8F, ARGB: 255, 188, 143, 143) + /// + public static readonly Color RosyBrown = FromArgb(255, 188, 143, 143); + + /// + /// RoyalBlue (Hex #FF4169E1, ARGB: 255, 65, 105, 225) + /// + public static readonly Color RoyalBlue = FromArgb(255, 65, 105, 225); + + /// + /// SaddleBrown (Hex #FF8B4513, ARGB: 255, 139, 69, 19) + /// + public static readonly Color SaddleBrown = FromArgb(255, 139, 69, 19); + + /// + /// Salmon (Hex #FFFA8072, ARGB: 255, 250, 128, 114) + /// + public static readonly Color Salmon = FromArgb(255, 250, 128, 114); + + /// + /// SandyBrown (Hex #FFF4A460, ARGB: 255, 244, 164, 96) + /// + public static readonly Color SandyBrown = FromArgb(255, 244, 164, 96); + + /// + /// SeaGreen (Hex #FF2E8B57, ARGB: 255, 46, 139, 87) + /// + public static readonly Color SeaGreen = FromArgb(255, 46, 139, 87); + + /// + /// SeaShell (Hex #FFFFF5EE, ARGB: 255, 255, 245, 238) + /// + public static readonly Color SeaShell = FromArgb(255, 255, 245, 238); + + /// + /// Sienna (Hex #FFA0522D, ARGB: 255, 160, 82, 45) + /// + public static readonly Color Sienna = FromArgb(255, 160, 82, 45); + + /// + /// Silver (Hex #FFC0C0C0, ARGB: 255, 192, 192, 192) + /// + public static readonly Color Silver = FromArgb(255, 192, 192, 192); + + /// + /// SkyBlue (Hex #FF87CEEB, ARGB: 255, 135, 206, 235) + /// + public static readonly Color SkyBlue = FromArgb(255, 135, 206, 235); + + /// + /// SlateBlue (Hex #FF6A5ACD, ARGB: 255, 106, 90, 205) + /// + public static readonly Color SlateBlue = FromArgb(255, 106, 90, 205); + + /// + /// SlateGray (Hex #FF708090, ARGB: 255, 112, 128, 144) + /// + public static readonly Color SlateGray = FromArgb(255, 112, 128, 144); + + /// + /// Snow (Hex #FFFFFAFA, ARGB: 255, 255, 250, 250) + /// + public static readonly Color Snow = FromArgb(255, 255, 250, 250); + + /// + /// SpringGreen (Hex #FF00FF7F, ARGB: 255, 0, 255, 127) + /// + public static readonly Color SpringGreen = FromArgb(255, 0, 255, 127); + + /// + /// SteelBlue (Hex #FF4682B4, ARGB: 255, 70, 130, 180) + /// + public static readonly Color SteelBlue = FromArgb(255, 70, 130, 180); + + /// + /// Tan (Hex #FFD2B48C, ARGB: 255, 210, 180, 140) + /// + public static readonly Color Tan = FromArgb(255, 210, 180, 140); + + /// + /// Teal (Hex #FF008080, ARGB: 255, 0, 128, 128) + /// + public static readonly Color Teal = FromArgb(255, 0, 128, 128); + + /// + /// Thistle (Hex #FFD8BFD8, ARGB: 255, 216, 191, 216) + /// + public static readonly Color Thistle = FromArgb(255, 216, 191, 216); + + /// + /// Tomato (Hex #FFFF6347, ARGB: 255, 255, 99, 71) + /// + public static readonly Color Tomato = FromArgb(255, 255, 99, 71); + + /// + /// Transparent (Hex #00FFFFFF, ARGB: 0, 255, 255, 255) + /// + public static readonly Color Transparent = FromArgb(0, 255, 255, 255); + + /// + /// Turquoise (Hex #FF40E0D0, ARGB: 255, 64, 224, 208) + /// + public static readonly Color Turquoise = FromArgb(255, 64, 224, 208); + + /// + /// Violet (Hex #FFEE82EE, ARGB: 255, 238, 130, 238) + /// + public static readonly Color Violet = FromArgb(255, 238, 130, 238); + + /// + /// Wheat (Hex #FFF5DEB3, ARGB: 255, 245, 222, 179) + /// + public static readonly Color Wheat = FromArgb(255, 245, 222, 179); + + /// + /// White (Hex #FFFFFFFF, ARGB: 255, 255, 255, 255) + /// + public static readonly Color White = FromArgb(255, 255, 255, 255); + + /// + /// WhiteSmoke (Hex #FFF5F5F5, ARGB: 255, 245, 245, 245) + /// + public static readonly Color WhiteSmoke = FromArgb(255, 245, 245, 245); + + /// + /// Yellow (Hex #FFFFFF00, ARGB: 255, 255, 255, 0) + /// + public static readonly Color Yellow = FromArgb(255, 255, 255, 0); + + /// + /// YellowGreen (Hex #FF9ACD32, ARGB: 255, 154, 205, 50) + /// + public static readonly Color YellowGreen = FromArgb(255, 154, 205, 50); } \ No newline at end of file diff --git a/src/Simplexcel/ColumnWidthCollection.cs b/src/Simplexcel/ColumnWidthCollection.cs index 223d83a..1ff27a2 100644 --- a/src/Simplexcel/ColumnWidthCollection.cs +++ b/src/Simplexcel/ColumnWidthCollection.cs @@ -1,52 +1,51 @@ using System.Collections; using System.Collections.Generic; -namespace Simplexcel +namespace Simplexcel; + +/// +/// Custom Column Widths within a worksheet +/// +public sealed class ColumnWidthCollection : IEnumerable> { + private readonly Dictionary _columnWidths = new Dictionary(); + /// - /// Custom Column Widths within a worksheet + /// Get or set the width of a column (Zero-based column index, null value = auto) /// - public sealed class ColumnWidthCollection : IEnumerable> + /// Zero-based column index + /// + public double? this[int column] { - private readonly Dictionary _columnWidths = new Dictionary(); - - /// - /// Get or set the width of a column (Zero-based column index, null value = auto) - /// - /// Zero-based column index - /// - public double? this[int column] + get { return _columnWidths.ContainsKey(column) ? _columnWidths[column] : (double?)null; } + set { - get { return _columnWidths.ContainsKey(column) ? _columnWidths[column] : (double?)null; } - set + if (!value.HasValue) + { + _columnWidths.Remove(column); + } + else { - if (!value.HasValue) - { - _columnWidths.Remove(column); - } - else - { - _columnWidths[column] = value.Value; - } + _columnWidths[column] = value.Value; } } + } - /// - /// Enumerate over the custom column widths. The Key is the zero-based column, the value is the custom column width. - /// - /// - public IEnumerator> GetEnumerator() - { - return _columnWidths.GetEnumerator(); - } + /// + /// Enumerate over the custom column widths. The Key is the zero-based column, the value is the custom column width. + /// + /// + public IEnumerator> GetEnumerator() + { + return _columnWidths.GetEnumerator(); + } - /// - /// Enumerate over the custom column widths. The Key is the zero-based column, the value is the custom column width. - /// - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + /// Enumerate over the custom column widths. The Key is the zero-based column, the value is the custom column width. + /// + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/ExtensionMethods.Internal.cs b/src/Simplexcel/ExtensionMethods.Internal.cs index daa24ff..2aac121 100644 --- a/src/Simplexcel/ExtensionMethods.Internal.cs +++ b/src/Simplexcel/ExtensionMethods.Internal.cs @@ -2,74 +2,73 @@ using System.Collections.Generic; using System.Reflection; -namespace Simplexcel +namespace Simplexcel; + +internal static class ExtensionMethods { - internal static class ExtensionMethods - { - internal static IEnumerable GetAllProperties(this TypeInfo typeInfo) => GetAllForType(typeInfo, ti => ti.DeclaredProperties); + internal static IEnumerable GetAllProperties(this TypeInfo typeInfo) => GetAllForType(typeInfo, ti => ti.DeclaredProperties); - private static IEnumerable GetAllForType(TypeInfo typeInfo, Func> accessor) + private static IEnumerable GetAllForType(TypeInfo typeInfo, Func> accessor) + { + if (typeInfo == null) { - if (typeInfo == null) - { - yield break; - } + yield break; + } - // The Stack is to make sure that we fetch Base Type Properties first - var baseTypes = new Stack(); - while (typeInfo != null) - { - baseTypes.Push(typeInfo); - typeInfo = typeInfo.BaseType?.GetTypeInfo(); - } + // The Stack is to make sure that we fetch Base Type Properties first + var baseTypes = new Stack(); + while (typeInfo != null) + { + baseTypes.Push(typeInfo); + typeInfo = typeInfo.BaseType?.GetTypeInfo(); + } - while (baseTypes.Count > 0) + while (baseTypes.Count > 0) + { + var ti = baseTypes.Pop(); + foreach (var t in accessor(ti)) { - var ti = baseTypes.Pop(); - foreach (var t in accessor(ti)) - { - yield return t; - } + yield return t; } } + } - internal static int GetCollectionHashCode(this IEnumerable input) + internal static int GetCollectionHashCode(this IEnumerable input) + { + var hashCode = 0; + if (input != null) { - var hashCode = 0; - if (input != null) + foreach (var item in input) { - foreach (var item in input) - { - var itemHashCode = item == null ? 0 : item.GetHashCode(); - hashCode = (hashCode * 397) ^ itemHashCode; - } + var itemHashCode = item == null ? 0 : item.GetHashCode(); + hashCode = (hashCode * 397) ^ itemHashCode; } - return hashCode; } + return hashCode; + } - private static readonly char[] HexEncodingTable = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + private static readonly char[] HexEncodingTable = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - /// - /// Taken from Bouncy Castle and slightly changed to create a string rather than write to a stream. - /// https://www.bouncycastle.org/ - /// - /// Licensed under MIT License - /// https://www.bouncycastle.org/csharp/licence.html - /// - internal static string ToHexString(this IReadOnlyList bytes, int offset = 0, int length = -1) - { - if (bytes == null || bytes.Count == 0) { return string.Empty; } - if (length == -1) { length = bytes.Count; } + /// + /// Taken from Bouncy Castle and slightly changed to create a string rather than write to a stream. + /// https://www.bouncycastle.org/ + /// + /// Licensed under MIT License + /// https://www.bouncycastle.org/csharp/licence.html + /// + internal static string ToHexString(this IReadOnlyList bytes, int offset = 0, int length = -1) + { + if (bytes == null || bytes.Count == 0) { return string.Empty; } + if (length == -1) { length = bytes.Count; } - var result = new char[2 * length]; - var index = offset; - for (int i = offset; i < (offset + length); i++) - { - var v = bytes[i]; - result[index++] = HexEncodingTable[v >> 4]; - result[index++] = HexEncodingTable[v & 0xf]; - } - return new string(result); + var result = new char[2 * length]; + var index = offset; + for (int i = offset; i < (offset + length); i++) + { + var v = bytes[i]; + result[index++] = HexEncodingTable[v >> 4]; + result[index++] = HexEncodingTable[v & 0xf]; } + return new string(result); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/ExtensionMethods.cs b/src/Simplexcel/ExtensionMethods.cs index 8e2d0d6..3607b40 100644 --- a/src/Simplexcel/ExtensionMethods.cs +++ b/src/Simplexcel/ExtensionMethods.cs @@ -2,55 +2,54 @@ using System.Linq; using System.Reflection; -namespace Simplexcel +namespace Simplexcel; + +/// +/// Some utility methods that don't need to be in their respective classes +/// +public static class SimplexcelExtensionMethods { /// - /// Some utility methods that don't need to be in their respective classes + /// Insert a manual page break after the row specified by the cell address (e.g., B5) /// - public static class SimplexcelExtensionMethods + /// + /// + public static void InsertManualPageBreakAfterRow(this Worksheet sheet, string cellAddress) { - /// - /// Insert a manual page break after the row specified by the cell address (e.g., B5) - /// - /// - /// - public static void InsertManualPageBreakAfterRow(this Worksheet sheet, string cellAddress) - { - CellAddressHelper.ReferenceToColRow(cellAddress, out int row, out _); - sheet.InsertManualPageBreakAfterRow(row+1); - } + CellAddressHelper.ReferenceToColRow(cellAddress, out int row, out _); + sheet.InsertManualPageBreakAfterRow(row+1); + } - /// - /// Insert a manual page break to the left of the column specified by the cell address (e.g., B5) - /// - /// - /// - public static void InsertManualPageBreakAfterColumn(this Worksheet sheet, string cellAddress) - { - CellAddressHelper.ReferenceToColRow(cellAddress, out _, out int col); - sheet.InsertManualPageBreakAfterColumn(col+1); - } + /// + /// Insert a manual page break to the left of the column specified by the cell address (e.g., B5) + /// + /// + /// + public static void InsertManualPageBreakAfterColumn(this Worksheet sheet, string cellAddress) + { + CellAddressHelper.ReferenceToColRow(cellAddress, out _, out int col); + sheet.InsertManualPageBreakAfterColumn(col+1); + } - private readonly static Type XlsxIgnoreColumnType = typeof(XlsxIgnoreColumnAttribute); - private readonly static Type XlsxColumnType = typeof(XlsxColumnAttribute); - internal static bool HasXlsxIgnoreAttribute(this PropertyInfo prop) + private readonly static Type XlsxIgnoreColumnType = typeof(XlsxIgnoreColumnAttribute); + private readonly static Type XlsxColumnType = typeof(XlsxColumnAttribute); + internal static bool HasXlsxIgnoreAttribute(this PropertyInfo prop) + { + if(prop == null) { - if(prop == null) - { - throw new ArgumentNullException(nameof(prop)); - } - - return prop.GetCustomAttributes(XlsxIgnoreColumnType).Any(); + throw new ArgumentNullException(nameof(prop)); } - internal static XlsxColumnAttribute GetXlsxColumnAttribute(this PropertyInfo prop) - { - if (prop == null) - { - throw new ArgumentNullException(nameof(prop)); - } + return prop.GetCustomAttributes(XlsxIgnoreColumnType).Any(); + } - return prop.GetCustomAttributes(XlsxColumnType).Cast().FirstOrDefault(); + internal static XlsxColumnAttribute GetXlsxColumnAttribute(this PropertyInfo prop) + { + if (prop == null) + { + throw new ArgumentNullException(nameof(prop)); } + + return prop.GetCustomAttributes(XlsxColumnType).Cast().FirstOrDefault(); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/LargeNumberHandlingMode.cs b/src/Simplexcel/LargeNumberHandlingMode.cs index 9665372..8bf27df 100644 --- a/src/Simplexcel/LargeNumberHandlingMode.cs +++ b/src/Simplexcel/LargeNumberHandlingMode.cs @@ -1,24 +1,23 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// Excel doesn't want to display large numbers (more than 11 digits) properly: +/// https://support.microsoft.com/en-us/help/2643223/long-numbers-are-displayed-incorrectly-in-excel +/// +/// This decides how to handle larger numbers. +/// See and for the actual limits. +/// +public enum LargeNumberHandlingMode { /// - /// Excel doesn't want to display large numbers (more than 11 digits) properly: - /// https://support.microsoft.com/en-us/help/2643223/long-numbers-are-displayed-incorrectly-in-excel - /// - /// This decides how to handle larger numbers. - /// See and for the actual limits. + /// Force the number to be stored as Text (Default) /// - public enum LargeNumberHandlingMode - { - /// - /// Force the number to be stored as Text (Default) - /// - StoreAsText = 0, + StoreAsText = 0, - /// - /// Do not do anything different, and store numbers as-is. - /// This may cause Excel to truncate the number. - /// This was the behavior before Simplexcel Version 2.1.0. - /// - None - } -} + /// + /// Do not do anything different, and store numbers as-is. + /// This may cause Excel to truncate the number. + /// This was the behavior before Simplexcel Version 2.1.0. + /// + None +} \ No newline at end of file diff --git a/src/Simplexcel/PageSetup/Orientation.cs b/src/Simplexcel/PageSetup/Orientation.cs index 5e9cc91..9d46fb8 100644 --- a/src/Simplexcel/PageSetup/Orientation.cs +++ b/src/Simplexcel/PageSetup/Orientation.cs @@ -1,17 +1,16 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// The Orientation of a page +/// +public enum Orientation { /// - /// The Orientation of a page + /// Portrait (default) /// - public enum Orientation - { - /// - /// Portrait (default) - /// - Portrait = 0, - /// - /// Landscape - /// - Landscape - } -} + Portrait = 0, + /// + /// Landscape + /// + Landscape +} \ No newline at end of file diff --git a/src/Simplexcel/PageSetup/PageBreak.cs b/src/Simplexcel/PageSetup/PageBreak.cs index 09c90c8..9949363 100644 --- a/src/Simplexcel/PageSetup/PageBreak.cs +++ b/src/Simplexcel/PageSetup/PageBreak.cs @@ -1,38 +1,37 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// The brk element, for Page Breaks +/// +public sealed class PageBreak { /// - /// The brk element, for Page Breaks + /// Zero-based row or column Id of the page break. + /// Breaks occur above the specified row and left of the specified column. /// - public sealed class PageBreak - { - /// - /// Zero-based row or column Id of the page break. - /// Breaks occur above the specified row and left of the specified column. - /// - public int Id { get; set; } + public int Id { get; set; } - /// - /// Manual Break flag. true means the break is a manually inserted break. - /// - public bool IsManualBreak { get; set; } + /// + /// Manual Break flag. true means the break is a manually inserted break. + /// + public bool IsManualBreak { get; set; } - /// - /// Zero-based index of end row or column of the break. - /// For row breaks, specifies column index; - /// for column breaks, specifies row index. - /// - public int Max { get; set; } + /// + /// Zero-based index of end row or column of the break. + /// For row breaks, specifies column index; + /// for column breaks, specifies row index. + /// + public int Max { get; set; } - /// - /// Zero-based index of start row or column of the break. - /// For row breaks, specifies column index; - /// for column breaks, specifies row index. - /// - public int Min { get; set; } + /// + /// Zero-based index of start row or column of the break. + /// For row breaks, specifies column index; + /// for column breaks, specifies row index. + /// + public int Min { get; set; } - /// - /// Flag indicating that a PivotTable created this break. - /// - public bool IsPivotCreatedPageBreak { get; set; } - } -} + /// + /// Flag indicating that a PivotTable created this break. + /// + public bool IsPivotCreatedPageBreak { get; set; } +} \ No newline at end of file diff --git a/src/Simplexcel/PageSetup/PageSetup.cs b/src/Simplexcel/PageSetup/PageSetup.cs index bbfb880..4b9f0ce 100644 --- a/src/Simplexcel/PageSetup/PageSetup.cs +++ b/src/Simplexcel/PageSetup/PageSetup.cs @@ -1,23 +1,22 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// Page Setup for a Sheet +/// +public sealed class PageSetup { /// - /// Page Setup for a Sheet + /// How many rows to repeat on each page when printing? /// - public sealed class PageSetup - { - /// - /// How many rows to repeat on each page when printing? - /// - public int PrintRepeatRows { get; set; } + public int PrintRepeatRows { get; set; } - /// - /// How many columns to repeat on each page when printing? - /// - public int PrintRepeatColumns { get; set; } + /// + /// How many columns to repeat on each page when printing? + /// + public int PrintRepeatColumns { get; set; } - /// - /// The Orientation of the page, Portrait (default) or Landscape - /// - public Orientation Orientation { get; set; } - } -} + /// + /// The Orientation of the page, Portrait (default) or Landscape + /// + public Orientation Orientation { get; set; } +} \ No newline at end of file diff --git a/src/Simplexcel/Simplexcel.csproj b/src/Simplexcel/Simplexcel.csproj index 2fef673..2b8f06d 100644 --- a/src/Simplexcel/Simplexcel.csproj +++ b/src/Simplexcel/Simplexcel.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netstandard2.1;net45 3.0.0 @@ -22,7 +22,7 @@ true ../simplexcel_oss.snk false - 8.0 + 12 diff --git a/src/Simplexcel/SimplexcelVersion.cs b/src/Simplexcel/SimplexcelVersion.cs index f3468b0..b9e4eaf 100644 --- a/src/Simplexcel/SimplexcelVersion.cs +++ b/src/Simplexcel/SimplexcelVersion.cs @@ -2,50 +2,49 @@ using System.Diagnostics; using System.Reflection; -namespace Simplexcel +namespace Simplexcel; + +/// +/// The version of the Simplexcel Library. +/// +public static class SimplexcelVersion { /// /// The version of the Simplexcel Library. + /// This might include a suffix for development versions, e.g., "2.3.0.177-v3-dev" /// - public static class SimplexcelVersion - { - /// - /// The version of the Simplexcel Library. - /// This might include a suffix for development versions, e.g., "2.3.0.177-v3-dev" - /// - public static string VersionString { get; } + public static string VersionString { get; } - /// - /// The version of the Simplexcel Library. - /// This does not indicate if this is a development version. - /// - public static Version Version { get; } + /// + /// The version of the Simplexcel Library. + /// This does not indicate if this is a development version. + /// + public static Version Version { get; } - /// - /// The Public Key that was used when signing Simplexcel - /// - public static string PublicKey { get; } + /// + /// The Public Key that was used when signing Simplexcel + /// + public static string PublicKey { get; } - /// - /// The Public Key Token that was used when signing Simplexcel - /// - public static string PublicKeyToken { get; } + /// + /// The Public Key Token that was used when signing Simplexcel + /// + public static string PublicKeyToken { get; } - static SimplexcelVersion() - { - // AssemblyVersion is always 3.0.0.0 due to strong naming - // [assembly: AssemblyVersion("3.0.0.0")] - // [assembly: AssemblyFileVersion("3.0.0.177")] - // [assembly: AssemblyInformationalVersion("3.0.0.177-v3-dev")] - var assembly = typeof(SimplexcelVersion).Assembly; - var asmName = assembly.GetName(); - PublicKey = asmName.GetPublicKey().ToHexString(); - PublicKeyToken = asmName.GetPublicKeyToken().ToHexString(); + static SimplexcelVersion() + { + // AssemblyVersion is always 3.0.0.0 due to strong naming + // [assembly: AssemblyVersion("3.0.0.0")] + // [assembly: AssemblyFileVersion("3.0.0.177")] + // [assembly: AssemblyInformationalVersion("3.0.0.177-v3-dev")] + var assembly = typeof(SimplexcelVersion).Assembly; + var asmName = assembly.GetName(); + PublicKey = asmName.GetPublicKey().ToHexString(); + PublicKeyToken = asmName.GetPublicKeyToken().ToHexString(); - var infoVersion = assembly.GetCustomAttribute(); - var fileVersion = assembly.GetCustomAttribute(); - Version = fileVersion != null ? new Version(fileVersion.Version) : asmName.Version; - VersionString = infoVersion?.InformationalVersion ?? asmName.Version.ToString(); - } + var infoVersion = assembly.GetCustomAttribute(); + var fileVersion = assembly.GetCustomAttribute(); + Version = fileVersion != null ? new Version(fileVersion.Version) : asmName.Version; + VersionString = infoVersion?.InformationalVersion ?? asmName.Version.ToString(); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/Workbook.cs b/src/Simplexcel/Workbook.cs index 7d30ef0..c8c711d 100644 --- a/src/Simplexcel/Workbook.cs +++ b/src/Simplexcel/Workbook.cs @@ -4,104 +4,103 @@ using System.Linq; using Simplexcel.XlsxInternal; -namespace Simplexcel +namespace Simplexcel; + +/// +/// An Excel Workbook +/// +public sealed class Workbook { + private readonly List _sheets = new List(); + /// - /// An Excel Workbook + /// The Worksheets in this Workbook /// - public sealed class Workbook - { - private readonly List _sheets = new List(); + public IEnumerable Sheets { get { return _sheets.AsEnumerable(); } } - /// - /// The Worksheets in this Workbook - /// - public IEnumerable Sheets { get { return _sheets.AsEnumerable(); } } + /// + /// The title of the Workbook + /// + public string Title { get; set; } - /// - /// The title of the Workbook - /// - public string Title { get; set; } + /// + /// The author of the Workbook + /// + public string Author { get; set; } - /// - /// The author of the Workbook - /// - public string Author { get; set; } + /// + /// How many sheets are in the Workbook currently? + /// + public int SheetCount + { + get { return _sheets.Count; } + } - /// - /// How many sheets are in the Workbook currently? - /// - public int SheetCount + /// + /// Add a worksheet to this workbook. Sheet names must be unique. + /// + /// Thrown if a sheet with the same Name already exists + /// + public void Add(Worksheet sheet) + { + // According to ECMA-376, sheet names must be unique + if (_sheets.Any(s => s.Name == sheet.Name)) { - get { return _sheets.Count; } + throw new ArgumentException("This workbook already contains a sheet named " + sheet.Name); } - /// - /// Add a worksheet to this workbook. Sheet names must be unique. - /// - /// Thrown if a sheet with the same Name already exists - /// - public void Add(Worksheet sheet) - { - // According to ECMA-376, sheet names must be unique - if (_sheets.Any(s => s.Name == sheet.Name)) - { - throw new ArgumentException("This workbook already contains a sheet named " + sheet.Name); - } + _sheets.Add(sheet); + } - _sheets.Add(sheet); + /// + /// Save this workbook to a file, overwriting the file if it exists + /// + /// + /// Use compression? (Smaller files/higher CPU Usage) + /// Thrown if there are no sheets in the workbook. + public void Save(string filename, bool compress = true) + { + if(string.IsNullOrEmpty(filename)) + { + throw new ArgumentException("Invalid Filename.", nameof(filename)); } - /// - /// Save this workbook to a file, overwriting the file if it exists - /// - /// - /// Use compression? (Smaller files/higher CPU Usage) - /// Thrown if there are no sheets in the workbook. - public void Save(string filename, bool compress = true) + using (var fs = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) { - if(string.IsNullOrEmpty(filename)) - { - throw new ArgumentException("Invalid Filename.", nameof(filename)); - } - - using (var fs = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) - { - Save(fs, compress); - } + Save(fs, compress); } + } - /// - /// Save this workbook to the given stream - /// - /// - /// Use compression? (Smaller files/higher CPU Usage) - /// Thrown if there are no sheets in the workbook. - public void Save(Stream stream, bool compress = true) + /// + /// Save this workbook to the given stream + /// + /// + /// Use compression? (Smaller files/higher CPU Usage) + /// Thrown if there are no sheets in the workbook. + public void Save(Stream stream, bool compress = true) + { + if(stream == null) { - if(stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } + throw new ArgumentNullException(nameof(stream)); + } - if(!stream.CanWrite) - { - throw new InvalidOperationException("Stream to save to is not writeable."); - } + if(!stream.CanWrite) + { + throw new InvalidOperationException("Stream to save to is not writeable."); + } - if (stream.CanSeek) - { - XlsxWriter.Save(this, stream, compress); - } - else + if (stream.CanSeek) + { + XlsxWriter.Save(this, stream, compress); + } + else + { + // ZipArchive needs a seekable stream. If a stream is not seekable (e.g., HttpContext.Response.OutputStream), wrap it in a MemoryStream instead. + // TODO: Can we guess the required capacity? + using(var ms = new MemoryStream()) { - // ZipArchive needs a seekable stream. If a stream is not seekable (e.g., HttpContext.Response.OutputStream), wrap it in a MemoryStream instead. - // TODO: Can we guess the required capacity? - using(var ms = new MemoryStream()) - { - XlsxWriter.Save(this, ms, compress); - ms.CopyTo(stream); - } + XlsxWriter.Save(this, ms, compress); + ms.CopyTo(stream); } } } diff --git a/src/Simplexcel/Worksheet.Populate.cs b/src/Simplexcel/Worksheet.Populate.cs index 8e3aa36..62ad142 100644 --- a/src/Simplexcel/Worksheet.Populate.cs +++ b/src/Simplexcel/Worksheet.Populate.cs @@ -4,146 +4,145 @@ using System.Linq; using System.Reflection; -namespace Simplexcel +namespace Simplexcel; + +public sealed partial class Worksheet { - public sealed partial class Worksheet + private readonly static Lazy>> PopulateCache + = new Lazy>>( + () => new ConcurrentDictionary>(), + System.Threading.LazyThreadSafetyMode.ExecutionAndPublication); + + /// + /// Create Worksheet with the provided data. + /// + /// Will use the Object Property Names as Column Headers (First Row) and then populate the cells with data. + /// You can use and to control the output. + /// + /// The name of the Sheet + /// The data, can be empty or null + /// If true, the Column info for the given type is being cached in memory + public static Worksheet FromData(string sheetName, IEnumerable data, bool cacheTypeColumns = false) where T : class { - private readonly static Lazy>> PopulateCache - = new Lazy>>( - () => new ConcurrentDictionary>(), - System.Threading.LazyThreadSafetyMode.ExecutionAndPublication); - - /// - /// Create Worksheet with the provided data. - /// - /// Will use the Object Property Names as Column Headers (First Row) and then populate the cells with data. - /// You can use and to control the output. - /// - /// The name of the Sheet - /// The data, can be empty or null - /// If true, the Column info for the given type is being cached in memory - public static Worksheet FromData(string sheetName, IEnumerable data, bool cacheTypeColumns = false) where T : class - { - var sheet = new Worksheet(sheetName); - sheet.Populate(data, cacheTypeColumns); - return sheet; - } + var sheet = new Worksheet(sheetName); + sheet.Populate(data, cacheTypeColumns); + return sheet; + } - /// - /// Populate Worksheet with the provided data. - /// - /// Will use the Object Property Names as Column Headers (First Row) and then populate the cells with data. - /// You can use and to control the output. - /// - /// The data, can be empty or null - /// If true, the Column info for the given type is being cached in memory - public void Populate(IEnumerable data, bool cacheTypeColumns = false) where T : class - { - data ??= Enumerable.Empty(); + /// + /// Populate Worksheet with the provided data. + /// + /// Will use the Object Property Names as Column Headers (First Row) and then populate the cells with data. + /// You can use and to control the output. + /// + /// The data, can be empty or null + /// If true, the Column info for the given type is being cached in memory + public void Populate(IEnumerable data, bool cacheTypeColumns = false) where T : class + { + data ??= Enumerable.Empty(); - var type = typeof(T); + var type = typeof(T); - // Key = TempColumnIndex, Value = Attribute - var cols = cacheTypeColumns ? TryGetFromCache(type) : null; - if (cols == null) + // Key = TempColumnIndex, Value = Attribute + var cols = cacheTypeColumns ? TryGetFromCache(type) : null; + if (cols == null) + { + cols = GetColumsFromType(type); + if (cacheTypeColumns) { - cols = GetColumsFromType(type); - if (cacheTypeColumns) - { - AddToPopulateCache(type, cols); - } + AddToPopulateCache(type, cols); } + } - foreach (var pi in cols.Values) - { - Cells[0, pi.ColumnIndex] = pi.Name; - Cells[0, pi.ColumnIndex].Bold = true; - } + foreach (var pi in cols.Values) + { + Cells[0, pi.ColumnIndex] = pi.Name; + Cells[0, pi.ColumnIndex].Bold = true; + } - var row = 0; - foreach (var item in data) + var row = 0; + foreach (var item in data) + { + row++; + + foreach (var pi in cols.Values) { - row++; - - foreach (var pi in cols.Values) - { - object val = pi.Property.GetValue(item); - var cell = Cell.FromObject(val); - Cells[row, pi.ColumnIndex] = cell; - } + object val = pi.Property.GetValue(item); + var cell = Cell.FromObject(val); + Cells[row, pi.ColumnIndex] = cell; } } + } - private static Dictionary GetColumsFromType(Type type) + private static Dictionary GetColumsFromType(Type type) + { + var cols = new Dictionary(); + var props = type.GetTypeInfo().GetAllProperties() + .Where(p => p.GetIndexParameters().Length == 0) + .ToList(); + + int tempCol = 0; // Just a counter to keep the order of Properties the same + int maxCol = -1; // Largest Column that has XlsxColumnAttribute.ColumnIndex specified + foreach (var prop in props) { - var cols = new Dictionary(); - var props = type.GetTypeInfo().GetAllProperties() - .Where(p => p.GetIndexParameters().Length == 0) - .ToList(); - - int tempCol = 0; // Just a counter to keep the order of Properties the same - int maxCol = -1; // Largest Column that has XlsxColumnAttribute.ColumnIndex specified - foreach (var prop in props) + if (prop.HasXlsxIgnoreAttribute()) { - if (prop.HasXlsxIgnoreAttribute()) - { - continue; - } - - var pi = new PopulateCellInfo(); - var colInfo = prop.GetXlsxColumnAttribute(); - pi.Name = colInfo?.Name == null ? prop.Name : colInfo.Name; - pi.ColumnIndex = colInfo?.ColumnIndex != null ? colInfo.ColumnIndex : -1; // -1 will later be reassigned - pi.TempColumnIndex = tempCol++; - - if (pi.ColumnIndex > maxCol) - { - maxCol = pi.ColumnIndex; - } - - pi.Property = prop; - cols[pi.TempColumnIndex] = pi; + continue; } - // Slot any Columns without an order after maxCol - for (int i = 0; i < tempCol; i++) + var pi = new PopulateCellInfo(); + var colInfo = prop.GetXlsxColumnAttribute(); + pi.Name = colInfo?.Name == null ? prop.Name : colInfo.Name; + pi.ColumnIndex = colInfo?.ColumnIndex != null ? colInfo.ColumnIndex : -1; // -1 will later be reassigned + pi.TempColumnIndex = tempCol++; + + if (pi.ColumnIndex > maxCol) { - var pi = cols[i]; - if (pi.ColumnIndex == -1) - { - pi.ColumnIndex = ++maxCol; - } + maxCol = pi.ColumnIndex; } - return cols; + pi.Property = prop; + cols[pi.TempColumnIndex] = pi; } - private static void AddToPopulateCache(Type type, Dictionary cols) + // Slot any Columns without an order after maxCol + for (int i = 0; i < tempCol; i++) { - PopulateCache.Value.AddOrUpdate(type, cols, (u1, u2) => cols); - } - - private static Dictionary TryGetFromCache(Type type) - { - if (!PopulateCache.IsValueCreated) + var pi = cols[i]; + if (pi.ColumnIndex == -1) { - return null; + pi.ColumnIndex = ++maxCol; } + } - if (PopulateCache.Value.TryGetValue(type, out var cached)) - { - return cached; - } + return cols; + } + private static void AddToPopulateCache(Type type, Dictionary cols) + { + PopulateCache.Value.AddOrUpdate(type, cols, (u1, u2) => cols); + } + + private static Dictionary TryGetFromCache(Type type) + { + if (!PopulateCache.IsValueCreated) + { return null; } - private class PopulateCellInfo + if (PopulateCache.Value.TryGetValue(type, out var cached)) { - public string Name { get; set; } - public int ColumnIndex { get; set; } - public int TempColumnIndex { get; set; } - public PropertyInfo Property { get; set; } + return cached; } + + return null; + } + + private class PopulateCellInfo + { + public string Name { get; set; } + public int ColumnIndex { get; set; } + public int TempColumnIndex { get; set; } + public PropertyInfo Property { get; set; } } } \ No newline at end of file diff --git a/src/Simplexcel/Worksheet.cs b/src/Simplexcel/Worksheet.cs index 02aa420..45c4680 100644 --- a/src/Simplexcel/Worksheet.cs +++ b/src/Simplexcel/Worksheet.cs @@ -1,258 +1,257 @@ using System; using System.Collections.Generic; -namespace Simplexcel +namespace Simplexcel; + +/// +/// A single Worksheet in an Excel Document +/// +public sealed partial class Worksheet { - /// - /// A single Worksheet in an Excel Document - /// - public sealed partial class Worksheet - { - private readonly CellCollection _cells = new CellCollection(); - - private readonly ColumnWidthCollection _columnWidth = new ColumnWidthCollection(); - - private readonly PageSetup _pageSetup = new PageSetup(); - - private List _sheetViews; - private List _rowBreaks; - private List _columnBreaks; - - /// - /// Get a list of characters that are invalid to use in the Sheet Name - /// - /// - /// These chars are not part of the ECMA-376 standard, but imposed by Excel - /// - public static readonly char[] InvalidSheetNameChars = new[] { '\\', '/', '?', '*', '[', ']' }; - - /// - /// Get the maximum allowable length for a sheet name - /// - /// - /// This limit is not part of the ECMA-376 standard, but imposed by Excel - /// - public static readonly int MaxSheetNameLength = 31; - - /// - /// Create a new Worksheet with the given name - /// - /// Thrown if the name is null, empty, longer than characters or contains any character in - /// - public Worksheet(string name) - { - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException("Sheet name is null or empty"); - } + private readonly CellCollection _cells = new CellCollection(); - if (name.Length > MaxSheetNameLength) - { - throw new ArgumentException("Sheet name is longer than " + MaxSheetNameLength + " characters."); - } + private readonly ColumnWidthCollection _columnWidth = new ColumnWidthCollection(); - if (name.IndexOfAny(InvalidSheetNameChars) > -1) - { - throw new ArgumentException("Sheet Names can not contain any of these characters: " + string.Join(" ", InvalidSheetNameChars)); - } + private readonly PageSetup _pageSetup = new PageSetup(); - Name = name; - LargeNumberHandlingMode = LargeNumberHandlingMode.StoreAsText; - } + private List _sheetViews; + private List _rowBreaks; + private List _columnBreaks; - /// - /// Gets or sets the Name of the Worksheet - /// - public string Name { get; } + /// + /// Get a list of characters that are invalid to use in the Sheet Name + /// + /// + /// These chars are not part of the ECMA-376 standard, but imposed by Excel + /// + public static readonly char[] InvalidSheetNameChars = new[] { '\\', '/', '?', '*', '[', ']' }; - /// - /// The Page Orientation and some other related values - /// - public PageSetup PageSetup + /// + /// Get the maximum allowable length for a sheet name + /// + /// + /// This limit is not part of the ECMA-376 standard, but imposed by Excel + /// + public static readonly int MaxSheetNameLength = 31; + + /// + /// Create a new Worksheet with the given name + /// + /// Thrown if the name is null, empty, longer than characters or contains any character in + /// + public Worksheet(string name) + { + if (string.IsNullOrEmpty(name)) { - get { return _pageSetup; } + throw new ArgumentException("Sheet name is null or empty"); } - /// - /// The Cells of the Worksheet (zero based, [0,0] = A1) - /// - public CellCollection Cells + if (name.Length > MaxSheetNameLength) { - get { return _cells; } + throw new ArgumentException("Sheet name is longer than " + MaxSheetNameLength + " characters."); } - /// - /// The Width of individual columns (Zero-based, in Excel's Units) - /// - public ColumnWidthCollection ColumnWidths + if (name.IndexOfAny(InvalidSheetNameChars) > -1) { - get { return _columnWidth; } + throw new ArgumentException("Sheet Names can not contain any of these characters: " + string.Join(" ", InvalidSheetNameChars)); } - /// - /// How to handle numbers that are larger than or smaller than ? - /// - public LargeNumberHandlingMode LargeNumberHandlingMode { get; set; } - - /// - /// Whether to enable the AutoFilter feature on the first non-empty row. - /// - /// Defaults to . - /// - /// See Use AutoFilter to filter your data - public bool AutoFilter { get; set; } - - /// - /// Get the cell with the given cell reference, e.g. Get the cell "A1". May return NULL. - /// - /// - /// The Cell, or NULL of the Cell hasn't been created yet. - public Cell this[string address] + Name = name; + LargeNumberHandlingMode = LargeNumberHandlingMode.StoreAsText; + } + + /// + /// Gets or sets the Name of the Worksheet + /// + public string Name { get; } + + /// + /// The Page Orientation and some other related values + /// + public PageSetup PageSetup + { + get { return _pageSetup; } + } + + /// + /// The Cells of the Worksheet (zero based, [0,0] = A1) + /// + public CellCollection Cells + { + get { return _cells; } + } + + /// + /// The Width of individual columns (Zero-based, in Excel's Units) + /// + public ColumnWidthCollection ColumnWidths + { + get { return _columnWidth; } + } + + /// + /// How to handle numbers that are larger than or smaller than ? + /// + public LargeNumberHandlingMode LargeNumberHandlingMode { get; set; } + + /// + /// Whether to enable the AutoFilter feature on the first non-empty row. + /// + /// Defaults to . + /// + /// See Use AutoFilter to filter your data + public bool AutoFilter { get; set; } + + /// + /// Get the cell with the given cell reference, e.g. Get the cell "A1". May return NULL. + /// + /// + /// The Cell, or NULL of the Cell hasn't been created yet. + public Cell this[string address] + { + get { return Cells[address]; } + set { Cells[address] = value; } + } + + /// + /// Get the cell with the given zero based row and column, e.g. [0,0] returns the A1 cell. May return NULL. + /// + /// + /// + /// The Cell, or NULL of the Cell hasn't been created yet. + public Cell this[int row, int column] + { + get { return Cells[row, column]; } + set { Cells[row, column] = value; } + } + + /// + /// Freeze the top row, that is, create a that splits the first row (A) into a pane. + /// + public void FreezeTopRow() => FreezeTopLeft(1, 0, Panes.BottomLeft); + + /// + /// Freeze the first column, that is, create a that splits the first column (1) into a pane. + /// + public void FreezeLeftColumn() => FreezeTopLeft(0, 1, Panes.TopRight); + + /// + /// Freezes rows at the top and columns on the left. + /// + /// The number of rows to freeze. + /// The number of columns to freeze. + /// The pane that's selected in the sheet. Leave null to automatically determine this. + public void FreezeTopLeft(int rows, int columns, Panes? activePane = null) + { + // TODO: Eventually, support more SheetView functionality, right now, keep it simple. + if (_sheetViews != null) { - get { return Cells[address]; } - set { Cells[address] = value; } + throw new InvalidOperationException("You have already frozen rows and/or columns on this Worksheet."); } - /// - /// Get the cell with the given zero based row and column, e.g. [0,0] returns the A1 cell. May return NULL. - /// - /// - /// - /// The Cell, or NULL of the Cell hasn't been created yet. - public Cell this[int row, int column] + if (rows < 0) { - get { return Cells[row, column]; } - set { Cells[row, column] = value; } + throw new ArgumentOutOfRangeException(nameof(rows), "Rows cannot be negative."); } - /// - /// Freeze the top row, that is, create a that splits the first row (A) into a pane. - /// - public void FreezeTopRow() => FreezeTopLeft(1, 0, Panes.BottomLeft); - - /// - /// Freeze the first column, that is, create a that splits the first column (1) into a pane. - /// - public void FreezeLeftColumn() => FreezeTopLeft(0, 1, Panes.TopRight); - - /// - /// Freezes rows at the top and columns on the left. - /// - /// The number of rows to freeze. - /// The number of columns to freeze. - /// The pane that's selected in the sheet. Leave null to automatically determine this. - public void FreezeTopLeft(int rows, int columns, Panes? activePane = null) + if (columns < 0) { - // TODO: Eventually, support more SheetView functionality, right now, keep it simple. - if (_sheetViews != null) - { - throw new InvalidOperationException("You have already frozen rows and/or columns on this Worksheet."); - } + throw new ArgumentOutOfRangeException(nameof(columns), "Columns cannot be negative."); + } - if (rows < 0) - { - throw new ArgumentOutOfRangeException(nameof(rows), "Rows cannot be negative."); - } + if (columns == 0 && rows == 0) + { + return; + } - if (columns < 0) + if (!activePane.HasValue) + { + if (columns == 0) { - throw new ArgumentOutOfRangeException(nameof(columns), "Columns cannot be negative."); + activePane = Panes.BottomLeft; } - - if (columns == 0 && rows == 0) + else if (rows == 0) { - return; + activePane = Panes.TopRight; } - - if (!activePane.HasValue) + else { - if (columns == 0) - { - activePane = Panes.BottomLeft; - } - else if (rows == 0) - { - activePane = Panes.TopRight; - } - else - { - activePane = Panes.BottomRight; - } + activePane = Panes.BottomRight; } - - var sheetView = new SheetView - { - Pane = new Pane - { - ActivePane = activePane.Value, - XSplit = columns > 0 ? (int?)columns : null, - YSplit = rows > 0 ? (int?)rows : null, - TopLeftCell = CellAddressHelper.ColRowToReference(columns, rows), - State = PaneState.Frozen - } - }; - sheetView.AddSelection(new Selection { ActivePane = activePane.Value }); - AddSheetView(sheetView); } - private void AddSheetView(SheetView sheetView) + var sheetView = new SheetView { - if (_sheetViews == null) + Pane = new Pane { - _sheetViews = new List(); + ActivePane = activePane.Value, + XSplit = columns > 0 ? (int?)columns : null, + YSplit = rows > 0 ? (int?)rows : null, + TopLeftCell = CellAddressHelper.ColRowToReference(columns, rows), + State = PaneState.Frozen } - _sheetViews.Add(sheetView); - } + }; + sheetView.AddSelection(new Selection { ActivePane = activePane.Value }); + AddSheetView(sheetView); + } - internal ICollection GetSheetViews() + private void AddSheetView(SheetView sheetView) + { + if (_sheetViews == null) { - return _sheetViews; + _sheetViews = new List(); } + _sheetViews.Add(sheetView); + } - internal ICollection GetRowBreaks() - { - return _rowBreaks; - } + internal ICollection GetSheetViews() + { + return _sheetViews; + } + + internal ICollection GetRowBreaks() + { + return _rowBreaks; + } - internal ICollection GetColumnBreaks() + internal ICollection GetColumnBreaks() + { + return _columnBreaks; + } + + /// + /// Insert a manual page break after the row specified by the zero-based index (e.g., 0 for the first row) + /// + /// The zero-based row index (e.g., 0 for the first row) + public void InsertManualPageBreakAfterRow(int row) + { + if (_rowBreaks == null) { - return _columnBreaks; + _rowBreaks = new List(); } - /// - /// Insert a manual page break after the row specified by the zero-based index (e.g., 0 for the first row) - /// - /// The zero-based row index (e.g., 0 for the first row) - public void InsertManualPageBreakAfterRow(int row) + _rowBreaks.Add(new PageBreak { - if (_rowBreaks == null) - { - _rowBreaks = new List(); - } + Id = row, + IsManualBreak = true + }); + } - _rowBreaks.Add(new PageBreak - { - Id = row, - IsManualBreak = true - }); + /// + /// Insert a manual page break to the left of the column specified by the zero-based index (e.g., 0 for column A) + /// + /// The zero-based index of the column (e.g., 0 for column A) + public void InsertManualPageBreakAfterColumn(int col) + { + if (_columnBreaks == null) + { + _columnBreaks = new List(); } - /// - /// Insert a manual page break to the left of the column specified by the zero-based index (e.g., 0 for column A) - /// - /// The zero-based index of the column (e.g., 0 for column A) - public void InsertManualPageBreakAfterColumn(int col) + _columnBreaks.Add(new PageBreak { - if (_columnBreaks == null) - { - _columnBreaks = new List(); - } - - _columnBreaks.Add(new PageBreak - { - Id = col, - IsManualBreak = true - }); - } + Id = col, + IsManualBreak = true + }); } } \ No newline at end of file diff --git a/src/Simplexcel/WorksheetView/Pane.cs b/src/Simplexcel/WorksheetView/Pane.cs index b25e156..0ce02ac 100644 --- a/src/Simplexcel/WorksheetView/Pane.cs +++ b/src/Simplexcel/WorksheetView/Pane.cs @@ -1,40 +1,39 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// Worksheet view pane +/// +public sealed class Pane { + internal const int DefaultXSplit = 0; + internal const int DefaultYSplit = 0; + internal const Panes DefaultActivePane = Panes.TopLeft; + internal const PaneState DefaultState = PaneState.Split; + /// - /// Worksheet view pane + /// Horizontal position of the split, in 1/20th of a point; 0 (zero) if none. If the pane is frozen, + /// this value indicates the number of columns visible in the top pane. /// - public sealed class Pane - { - internal const int DefaultXSplit = 0; - internal const int DefaultYSplit = 0; - internal const Panes DefaultActivePane = Panes.TopLeft; - internal const PaneState DefaultState = PaneState.Split; - - /// - /// Horizontal position of the split, in 1/20th of a point; 0 (zero) if none. If the pane is frozen, - /// this value indicates the number of columns visible in the top pane. - /// - public int? XSplit { get; set; } + public int? XSplit { get; set; } - /// - /// Vertical position of the split, in 1/20th of a point; 0 (zero) if none. If the pane is frozen, - /// this value indicates the number of rows visible in the left pane - /// - public int? YSplit { get; set; } + /// + /// Vertical position of the split, in 1/20th of a point; 0 (zero) if none. If the pane is frozen, + /// this value indicates the number of rows visible in the left pane + /// + public int? YSplit { get; set; } - /// - /// The pane that is active. - /// - public Panes? ActivePane { get; set; } + /// + /// The pane that is active. + /// + public Panes? ActivePane { get; set; } - /// - /// Indicates whether the pane has horizontal / vertical splits, and whether those splits are frozen. - /// - public PaneState? State { get; set; } + /// + /// Indicates whether the pane has horizontal / vertical splits, and whether those splits are frozen. + /// + public PaneState? State { get; set; } - /// - /// Location of the top left visible cell in the bottom right pane (when in Left-To-Right mode) - /// - public string TopLeftCell { get; set; } - } + /// + /// Location of the top left visible cell in the bottom right pane (when in Left-To-Right mode) + /// + public string TopLeftCell { get; set; } } \ No newline at end of file diff --git a/src/Simplexcel/WorksheetView/PaneState.cs b/src/Simplexcel/WorksheetView/PaneState.cs index 19e4a11..659e34a 100644 --- a/src/Simplexcel/WorksheetView/PaneState.cs +++ b/src/Simplexcel/WorksheetView/PaneState.cs @@ -1,27 +1,26 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// State of the sheet's pane. +/// +public enum PaneState { /// - /// State of the sheet's pane. + /// Panes are split, but not frozen. In this state, the split bars are adjustable by the user. /// - public enum PaneState - { - /// - /// Panes are split, but not frozen. In this state, the split bars are adjustable by the user. - /// - Split, + Split, - /// - /// Panes are frozen, but were not split being frozen. In - /// this state, when the panes are unfrozen again, a single - /// pane results, with no split. - /// - Frozen, + /// + /// Panes are frozen, but were not split being frozen. In + /// this state, when the panes are unfrozen again, a single + /// pane results, with no split. + /// + Frozen, - /// - /// Panes are frozen and were split before being frozen. In - /// this state, when the panes are unfrozen again, the split - /// remains, but is adjustable. - /// - FrozenSplit - } + /// + /// Panes are frozen and were split before being frozen. In + /// this state, when the panes are unfrozen again, the split + /// remains, but is adjustable. + /// + FrozenSplit } \ No newline at end of file diff --git a/src/Simplexcel/WorksheetView/Panes.cs b/src/Simplexcel/WorksheetView/Panes.cs index a3f3c8d..e47dbb4 100644 --- a/src/Simplexcel/WorksheetView/Panes.cs +++ b/src/Simplexcel/WorksheetView/Panes.cs @@ -1,44 +1,43 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// Defines the names of the four possible panes into which the view of a workbook in the application can be split. +/// +public enum Panes { /// - /// Defines the names of the four possible panes into which the view of a workbook in the application can be split. + /// Bottom right pane, when both vertical and horizontal splits are applied. /// - public enum Panes - { - /// - /// Bottom right pane, when both vertical and horizontal splits are applied. - /// - BottomRight, + BottomRight, - /// - /// Top right pane, when both vertical and horizontal splits are applied. - /// - /// This value is also used when only a vertical split has - /// been applied, dividing the pane into right and left - /// regions. In that case, this value specifies the right pane. - /// - TopRight, + /// + /// Top right pane, when both vertical and horizontal splits are applied. + /// + /// This value is also used when only a vertical split has + /// been applied, dividing the pane into right and left + /// regions. In that case, this value specifies the right pane. + /// + TopRight, - /// - /// Bottom left pane, when both vertical and horizontal splits are applied. - /// - /// This value is also used when only a horizontal split has - /// been applied, dividing the pane into upper and lower - /// regions. In that case, this value specifies the bottom pane - /// - BottomLeft, + /// + /// Bottom left pane, when both vertical and horizontal splits are applied. + /// + /// This value is also used when only a horizontal split has + /// been applied, dividing the pane into upper and lower + /// regions. In that case, this value specifies the bottom pane + /// + BottomLeft, - /// - /// Top left pane, when both vertical and horizontal splits are applied. - /// - /// This value is also used when only a horizontal split has - /// been applied, dividing the pane into upper and lower - /// regions. In that case, this value specifies the top pane. - /// - /// This value is also used when only a vertical split has - /// been applied, dividing the pane into right and left - /// regions. In that case, this value specifies the left pane - /// - TopLeft - } + /// + /// Top left pane, when both vertical and horizontal splits are applied. + /// + /// This value is also used when only a horizontal split has + /// been applied, dividing the pane into upper and lower + /// regions. In that case, this value specifies the top pane. + /// + /// This value is also used when only a vertical split has + /// been applied, dividing the pane into right and left + /// regions. In that case, this value specifies the left pane + /// + TopLeft } \ No newline at end of file diff --git a/src/Simplexcel/WorksheetView/Selection.cs b/src/Simplexcel/WorksheetView/Selection.cs index 476588c..cf47037 100644 --- a/src/Simplexcel/WorksheetView/Selection.cs +++ b/src/Simplexcel/WorksheetView/Selection.cs @@ -1,29 +1,28 @@ -namespace Simplexcel +namespace Simplexcel; + +/// +/// Worksheet view selection. +/// +public sealed class Selection { - /// - /// Worksheet view selection. - /// - public sealed class Selection - { - /// - /// The pane to which this selection belongs. - /// - public Panes ActivePane { get; set; } + /// + /// The pane to which this selection belongs. + /// + public Panes ActivePane { get; set; } - /// - /// Location of the active cell. E.g., "A1" - /// - public string ActiveCell { get; set; } + /// + /// Location of the active cell. E.g., "A1" + /// + public string ActiveCell { get; set; } - // activeCellId - // sqref + // activeCellId + // sqref - /// - /// Create a new selection - /// - public Selection() - { - ActivePane = Panes.TopLeft; - } - } -} + /// + /// Create a new selection + /// + public Selection() + { + ActivePane = Panes.TopLeft; + } +} \ No newline at end of file diff --git a/src/Simplexcel/WorksheetView/SheetView.cs b/src/Simplexcel/WorksheetView/SheetView.cs index d76776f..217e02e 100644 --- a/src/Simplexcel/WorksheetView/SheetView.cs +++ b/src/Simplexcel/WorksheetView/SheetView.cs @@ -1,85 +1,84 @@ using System; using System.Collections.Generic; -namespace Simplexcel +namespace Simplexcel; + +/// +/// A single sheet view definition. When more than one sheet view is defined in the file, it means that when opening +/// the workbook, each sheet view corresponds to a separate window within the spreadsheet application, where +/// each window is showing the particular sheet containing the same workbookViewId value, the last sheetView +/// definition is loaded, and the others are discarded. +/// +/// When multiple windows are viewing the same sheet, multiple +/// sheetView elements (with corresponding workbookView entries) are saved. +/// +public sealed class SheetView { - /// - /// A single sheet view definition. When more than one sheet view is defined in the file, it means that when opening - /// the workbook, each sheet view corresponds to a separate window within the spreadsheet application, where - /// each window is showing the particular sheet containing the same workbookViewId value, the last sheetView - /// definition is loaded, and the others are discarded. - /// - /// When multiple windows are viewing the same sheet, multiple - /// sheetView elements (with corresponding workbookView entries) are saved. - /// - public sealed class SheetView - { - private List _selections; + private List _selections; - internal ICollection Selections - { - get { return _selections; } - } + internal ICollection Selections + { + get { return _selections; } + } - /// - /// Flag indicating whether this sheet is selected. - /// When only 1 sheet is selected and active, this value should be in synch with the activeTab value. - /// In case of a conflict, the Start Part setting wins and sets the active sheet tab. - /// Multiple sheets can be selected, but only one sheet shall be active at one time. - /// - public bool? TabSelected { get; set; } + /// + /// Flag indicating whether this sheet is selected. + /// When only 1 sheet is selected and active, this value should be in synch with the activeTab value. + /// In case of a conflict, the Start Part setting wins and sets the active sheet tab. + /// Multiple sheets can be selected, but only one sheet shall be active at one time. + /// + public bool? TabSelected { get; set; } - /// - /// Show the ruler in page layout view - /// - public bool? ShowRuler { get; set; } + /// + /// Show the ruler in page layout view + /// + public bool? ShowRuler { get; set; } - /// - /// Zero-based index of this workbook view, pointing to a workbookView element in the bookViews collection. - /// - public int WorkbookViewId { get { return 0; } } + /// + /// Zero-based index of this workbook view, pointing to a workbookView element in the bookViews collection. + /// + public int WorkbookViewId { get { return 0; } } - /// - /// The pane that this SheetView applies to - /// - public Pane Pane { get; set; } + /// + /// The pane that this SheetView applies to + /// + public Pane Pane { get; set; } - /// - /// Add the given selection to the SheetView - /// - /// - /// If true, will throw an if there is already a selection with the same . If false, overwrite the previous selection for that pane. - public void AddSelection(Selection sel, bool throwOnDuplicatePane = true) + /// + /// Add the given selection to the SheetView + /// + /// + /// If true, will throw an if there is already a selection with the same . If false, overwrite the previous selection for that pane. + public void AddSelection(Selection sel, bool throwOnDuplicatePane = true) + { + if (sel == null) { throw new ArgumentNullException(nameof(sel)); } + if (!Enum.IsDefined(typeof(Panes), sel.ActivePane)) { - if (sel == null) { throw new ArgumentNullException(nameof(sel)); } - if (!Enum.IsDefined(typeof(Panes), sel.ActivePane)) - { - throw new ArgumentOutOfRangeException(nameof(sel), "Not a valid ActivePane: " + sel.ActivePane); - } + throw new ArgumentOutOfRangeException(nameof(sel), "Not a valid ActivePane: " + sel.ActivePane); + } - // Up to 4 Selections, and the Selection Pane cannot already be in use - if (_selections == null) - { - _selections = new List(4); - } + // Up to 4 Selections, and the Selection Pane cannot already be in use + if (_selections == null) + { + _selections = new List(4); + } - foreach (var s in _selections) + foreach (var s in _selections) + { + if (s.ActivePane == sel.ActivePane) { - if (s.ActivePane == sel.ActivePane) + if (throwOnDuplicatePane) { - if (throwOnDuplicatePane) - { - throw new InvalidOperationException("There is already a Selection for ActivePane " + sel.ActivePane); - } - else - { - _selections.Remove(s); - break; - } + throw new InvalidOperationException("There is already a Selection for ActivePane " + sel.ActivePane); + } + else + { + _selections.Remove(s); + break; } } - - _selections.Add(sel); } + + _selections.Add(sel); } } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/Namespaces.cs b/src/Simplexcel/XlsxInternal/Namespaces.cs index 4ea8212..ffbb3aa 100644 --- a/src/Simplexcel/XlsxInternal/Namespaces.cs +++ b/src/Simplexcel/XlsxInternal/Namespaces.cs @@ -1,26 +1,25 @@ using System.Xml.Linq; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// Various xmlns namespaces +/// +internal static class Namespaces { - /// - /// Various xmlns namespaces - /// - internal static class Namespaces - { - internal static readonly XNamespace simplexcel = "http://stum.de/simplexcel/v1"; - internal static readonly XNamespace coreProperties = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; - internal static readonly XNamespace dc = "http://purl.org/dc/elements/1.1/"; - internal static readonly XNamespace dcterms = "http://purl.org/dc/terms/"; - internal static readonly XNamespace extendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; - internal static readonly XNamespace mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"; - internal static readonly XNamespace officeRelationships = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; - internal static readonly XNamespace relationship = "http://schemas.openxmlformats.org/package/2006/relationships"; - internal static readonly XNamespace vt = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"; - internal static readonly XNamespace workbook = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; - internal static readonly XNamespace workbookRelationship = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; - internal static readonly XNamespace x = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; - internal static readonly XNamespace x14ac = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"; - internal static readonly XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance"; - internal static readonly XNamespace contenttypes = "http://schemas.openxmlformats.org/package/2006/content-types"; - } + internal static readonly XNamespace simplexcel = "http://stum.de/simplexcel/v1"; + internal static readonly XNamespace coreProperties = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; + internal static readonly XNamespace dc = "http://purl.org/dc/elements/1.1/"; + internal static readonly XNamespace dcterms = "http://purl.org/dc/terms/"; + internal static readonly XNamespace extendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; + internal static readonly XNamespace mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"; + internal static readonly XNamespace officeRelationships = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; + internal static readonly XNamespace relationship = "http://schemas.openxmlformats.org/package/2006/relationships"; + internal static readonly XNamespace vt = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"; + internal static readonly XNamespace workbook = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; + internal static readonly XNamespace workbookRelationship = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; + internal static readonly XNamespace x = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; + internal static readonly XNamespace x14ac = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"; + internal static readonly XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance"; + internal static readonly XNamespace contenttypes = "http://schemas.openxmlformats.org/package/2006/content-types"; } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/Relationship.cs b/src/Simplexcel/XlsxInternal/Relationship.cs index 8ba4cc7..107a5ca 100644 --- a/src/Simplexcel/XlsxInternal/Relationship.cs +++ b/src/Simplexcel/XlsxInternal/Relationship.cs @@ -1,17 +1,16 @@ -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// A Relationship inside the Package +/// +internal class Relationship { - /// - /// A Relationship inside the Package - /// - internal class Relationship - { - public string Id { get; set; } - public XmlFile Target { get; set; } - public string Type { get; set; } + public string Id { get; set; } + public XmlFile Target { get; set; } + public string Type { get; set; } - public Relationship(RelationshipCounter counter) - { - Id = "r" + counter.GetNextId(); - } + public Relationship(RelationshipCounter counter) + { + Id = "r" + counter.GetNextId(); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/RelationshipCounter.cs b/src/Simplexcel/XlsxInternal/RelationshipCounter.cs index ffd5ad5..e801f13 100644 --- a/src/Simplexcel/XlsxInternal/RelationshipCounter.cs +++ b/src/Simplexcel/XlsxInternal/RelationshipCounter.cs @@ -1,13 +1,12 @@ -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +internal class RelationshipCounter { - internal class RelationshipCounter - { - private int _count; + private int _count; - internal int GetNextId() - { - _count++; - return _count; - } + internal int GetNextId() + { + _count++; + return _count; } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/SharedStrings.cs b/src/Simplexcel/XlsxInternal/SharedStrings.cs index 0c8bfd6..78cf4a9 100644 --- a/src/Simplexcel/XlsxInternal/SharedStrings.cs +++ b/src/Simplexcel/XlsxInternal/SharedStrings.cs @@ -5,86 +5,85 @@ using System.Xml.Linq; using System.Text.RegularExpressions; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// The Shared Strings table in an Excel document +/// +internal class SharedStrings { + private readonly Dictionary _sharedStrings = new Dictionary(StringComparer.Ordinal); + /// - /// The Shared Strings table in an Excel document + /// The number of Unique Strings /// - internal class SharedStrings - { - private readonly Dictionary _sharedStrings = new Dictionary(StringComparer.Ordinal); - - /// - /// The number of Unique Strings - /// - internal int UniqueCount { get { return _sharedStrings.Count; } } - /// - /// The number of total Strings (incl. duplicate uses of the same string) - /// - internal int Count { get; private set; } + internal int UniqueCount { get { return _sharedStrings.Count; } } + /// + /// The number of total Strings (incl. duplicate uses of the same string) + /// + internal int Count { get; private set; } - /// - /// Add a string to the shared strings table and return the index of that string - /// - /// - /// - internal int GetStringIndex(string input) + /// + /// Add a string to the shared strings table and return the index of that string + /// + /// + /// + internal int GetStringIndex(string input) + { + // NULL is treated as an empty string + if (input == null) { - // NULL is treated as an empty string - if (input == null) - { - input = string.Empty; - } + input = string.Empty; + } - Count++; - if (!_sharedStrings.ContainsKey(input)) - { - _sharedStrings[input] = _sharedStrings.Count; - } - return _sharedStrings[input]; + Count++; + if (!_sharedStrings.ContainsKey(input)) + { + _sharedStrings[input] = _sharedStrings.Count; } + return _sharedStrings[input]; + } - /// - /// Create the XmlFile for the Package. - /// - /// - internal XmlFile ToXmlFile() + /// + /// Create the XmlFile for the Package. + /// + /// + internal XmlFile ToXmlFile() + { + var file = new XmlFile { - var file = new XmlFile - { - ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", - Path = "xl/sharedStrings.xml" - }; + ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", + Path = "xl/sharedStrings.xml" + }; - var sst = new XDocument(new XElement(Namespaces.x + "sst", - new XAttribute("xmlns", Namespaces.x), - new XAttribute("count", Count), - new XAttribute("uniqueCount", UniqueCount) - )); + var sst = new XDocument(new XElement(Namespaces.x + "sst", + new XAttribute("xmlns", Namespaces.x), + new XAttribute("count", Count), + new XAttribute("uniqueCount", UniqueCount) + )); - foreach (var kvp in _sharedStrings.OrderBy(k => k.Value)) + foreach (var kvp in _sharedStrings.OrderBy(k => k.Value)) + { + var tElem = new XElement(Namespaces.x + "t") { - var tElem = new XElement(Namespaces.x + "t") - { - Value = kvp.Key - }; + Value = kvp.Key + }; - var siElem = new XElement(Namespaces.x + "si", tElem); - sst.Root.Add(siElem); - } + var siElem = new XElement(Namespaces.x + "si", tElem); + sst.Root.Add(siElem); + } - file.Content = sst; + file.Content = sst; - return file; - } + return file; + } - internal Relationship ToRelationship(RelationshipCounter relationshipCounter) + internal Relationship ToRelationship(RelationshipCounter relationshipCounter) + { + return new Relationship(relationshipCounter) { - return new Relationship(relationshipCounter) - { - Target = ToXmlFile(), - Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" - }; - } + Target = ToXmlFile(), + Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" + }; } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/SheetPackageInfo.cs b/src/Simplexcel/XlsxInternal/SheetPackageInfo.cs index 24bdaff..a7bce6f 100644 --- a/src/Simplexcel/XlsxInternal/SheetPackageInfo.cs +++ b/src/Simplexcel/XlsxInternal/SheetPackageInfo.cs @@ -1,22 +1,21 @@ -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// Information about a Worksheet in the Package +/// +internal class SheetPackageInfo { - /// - /// Information about a Worksheet in the Package - /// - internal class SheetPackageInfo - { - internal int SheetId { get; set; } - internal string RelationshipId { get; set; } - internal string SheetName { get; set; } - internal string RepeatRows { get; set; } - internal string RepeatCols { get; set; } + internal int SheetId { get; set; } + internal string RelationshipId { get; set; } + internal string SheetName { get; set; } + internal string RepeatRows { get; set; } + internal string RepeatCols { get; set; } - internal SheetPackageInfo() - { - RelationshipId = string.Empty; - SheetName = string.Empty; - RepeatRows = string.Empty; - RepeatCols = string.Empty; - } + internal SheetPackageInfo() + { + RelationshipId = string.Empty; + SheetName = string.Empty; + RepeatRows = string.Empty; + RepeatCols = string.Empty; } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/StyleWriter.cs b/src/Simplexcel/XlsxInternal/StyleWriter.cs index 9c18a0b..c7384ed 100644 --- a/src/Simplexcel/XlsxInternal/StyleWriter.cs +++ b/src/Simplexcel/XlsxInternal/StyleWriter.cs @@ -2,349 +2,348 @@ using System.Collections.Generic; using System.Xml.Linq; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +internal static class StyleWriter { - internal static class StyleWriter + // There are up to 164 built in number formats (0-163), all else are custom (164 and above) + private const int CustomFormatIndex = 164; + + /// + /// Standard format codes as defined in ECMA-376, 3rd Edition, Part 1, 18.8.30 numFmt (Number Format) + /// + private static Dictionary StandardFormatIds = new Dictionary { - // There are up to 164 built in number formats (0-163), all else are custom (164 and above) - private const int CustomFormatIndex = 164; + ["General"] = 0, + ["0"] = 1, + ["0.00"] = 2, + ["#,##0"] = 3, + ["#,##0.00"] = 4, + ["0%"] = 9, + ["0.00%"] = 10, + ["0.00E+00"] = 11, + ["# ?/?"] = 12, + ["# ??/??"] = 13, + ["mm-dd-yy"] = 14, + ["d-mmm-yy"] = 15, + ["d-mmm"] = 16, + ["mmm-yy"] = 17, + ["h:mm AM/PM"] = 18, + ["h:mm:ss AM/PM"] = 19, + ["h:mm"] = 20, + ["h:mm:ss"] = 21, + ["m/d/yy h:mm"] = 22, + ["#,##0 ;(#,##0)"] = 37, + ["#,##0 ;[Red](#,##0)"] = 38, + ["#,##0.00;(#,##0.00)"] = 39, + ["#,##0.00;[Red](#,##0.00)"] = 40, + ["mm:ss"] = 45, + ["[h]:mm:ss"] = 46, + ["mmss.0"] = 47, + ["##0.0E+0"] = 48, + ["@"] = 49, + }; + + /// + /// Create a styles.xml file + /// + /// + /// + internal static XmlFile CreateStyleXml(IList styles) + { + var numberFormats = new List(); + var uniqueBorders = new List { CellBorder.None }; + var uniqueFonts = new List { new XlsxFont() }; + // These two fills MUST exist as fill 0 (None) and 1 (Gray125) + var uniqueFills = new List { new PatternFill { PatternType = PatternType.None }, new PatternFill { PatternType = PatternType.Gray125 } }; - /// - /// Standard format codes as defined in ECMA-376, 3rd Edition, Part 1, 18.8.30 numFmt (Number Format) - /// - private static Dictionary StandardFormatIds = new Dictionary + foreach (var style in styles) { - ["General"] = 0, - ["0"] = 1, - ["0.00"] = 2, - ["#,##0"] = 3, - ["#,##0.00"] = 4, - ["0%"] = 9, - ["0.00%"] = 10, - ["0.00E+00"] = 11, - ["# ?/?"] = 12, - ["# ??/??"] = 13, - ["mm-dd-yy"] = 14, - ["d-mmm-yy"] = 15, - ["d-mmm"] = 16, - ["mmm-yy"] = 17, - ["h:mm AM/PM"] = 18, - ["h:mm:ss AM/PM"] = 19, - ["h:mm"] = 20, - ["h:mm:ss"] = 21, - ["m/d/yy h:mm"] = 22, - ["#,##0 ;(#,##0)"] = 37, - ["#,##0 ;[Red](#,##0)"] = 38, - ["#,##0.00;(#,##0.00)"] = 39, - ["#,##0.00;[Red](#,##0.00)"] = 40, - ["mm:ss"] = 45, - ["[h]:mm:ss"] = 46, - ["mmss.0"] = 47, - ["##0.0E+0"] = 48, - ["@"] = 49, - }; + if (style.Border != CellBorder.None && !uniqueBorders.Contains(style.Border)) + { + uniqueBorders.Add(style.Border); + } - /// - /// Create a styles.xml file - /// - /// - /// - internal static XmlFile CreateStyleXml(IList styles) - { - var numberFormats = new List(); - var uniqueBorders = new List { CellBorder.None }; - var uniqueFonts = new List { new XlsxFont() }; - // These two fills MUST exist as fill 0 (None) and 1 (Gray125) - var uniqueFills = new List { new PatternFill { PatternType = PatternType.None }, new PatternFill { PatternType = PatternType.Gray125 } }; + if (!numberFormats.Contains(style.Format) && !StandardFormatIds.ContainsKey(style.Format)) + { + numberFormats.Add(style.Format); + } - foreach (var style in styles) + if (style.Font != null && !uniqueFonts.Contains(style.Font)) { - if (style.Border != CellBorder.None && !uniqueBorders.Contains(style.Border)) - { - uniqueBorders.Add(style.Border); - } + uniqueFonts.Add(style.Font); + } - if (!numberFormats.Contains(style.Format) && !StandardFormatIds.ContainsKey(style.Format)) - { - numberFormats.Add(style.Format); - } + if (style.Fill != null && !uniqueFills.Contains(style.Fill)) + { + uniqueFills.Add(style.Fill); + } + } - if (style.Font != null && !uniqueFonts.Contains(style.Font)) - { - uniqueFonts.Add(style.Font); - } + var file = new XmlFile + { + ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", + Path = "xl/styles.xml" + }; - if (style.Fill != null && !uniqueFills.Contains(style.Fill)) - { - uniqueFills.Add(style.Fill); - } - } + var doc = new XDocument(new XElement(Namespaces.workbook + "styleSheet", new XAttribute("xmlns", Namespaces.workbook))); + + StyleAddNumFmtsElement(doc, numberFormats); + StyleAddFontsElement(doc, uniqueFonts); + StyleAddFillsElement(doc, uniqueFills); + StyleAddBordersElement(doc, uniqueBorders); + StyleAddCellStyleXfsElement(doc); + StyleAddCellXfsElement(doc, styles, uniqueBorders, numberFormats, uniqueFonts, uniqueFills); + + file.Content = doc; - var file = new XmlFile + return file; + } + + private static void StyleAddCellXfsElement(XDocument doc, IList styles, IList uniqueBorders, IList numberFormats, IList fontInfos, IList fills) + { + var cellXfs = new XElement(Namespaces.workbook + "cellXfs", new XAttribute("count", styles.Count)); + + // Default Cell Style, used to make sure the row numbers don't appear broken + cellXfs.Add(new XElement(Namespaces.workbook + "xf", + new XAttribute("numFmtId", 0), + new XAttribute("fontId", 0), + new XAttribute("fillId", 0), + new XAttribute("borderId", 0), + new XAttribute("xfId", 0))); + + foreach (var style in styles) + { + var numFmtId = StandardFormatIds.TryGetValue(style.Format, out var standardNumFmtId) ? standardNumFmtId : numberFormats.IndexOf(style.Format) + CustomFormatIndex; + var fontId = fontInfos.IndexOf(style.Font); + var fillId = fills.IndexOf(style.Fill); + var borderId = uniqueBorders.IndexOf(style.Border); + var xfId = 0; + + var elem = new XElement(Namespaces.workbook + "xf", + new XAttribute("numFmtId", numFmtId), + new XAttribute("fontId", fontId), + new XAttribute("fillId", fillId), + new XAttribute("borderId", borderId), + new XAttribute("xfId", xfId)); + + if (numFmtId > 0) { - ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", - Path = "xl/styles.xml" - }; + elem.Add(new XAttribute("applyNumberFormat", 1)); + } + if (fillId > 0) + { + elem.Add(new XAttribute("applyFill", 1)); + } + if (fontId > 0) + { + elem.Add(new XAttribute("applyFont", 1)); + } + if (borderId > 0) + { + elem.Add(new XAttribute("applyBorder", 1)); + } - var doc = new XDocument(new XElement(Namespaces.workbook + "styleSheet", new XAttribute("xmlns", Namespaces.workbook))); + AddAlignmentElement(elem, style); - StyleAddNumFmtsElement(doc, numberFormats); - StyleAddFontsElement(doc, uniqueFonts); - StyleAddFillsElement(doc, uniqueFills); - StyleAddBordersElement(doc, uniqueBorders); - StyleAddCellStyleXfsElement(doc); - StyleAddCellXfsElement(doc, styles, uniqueBorders, numberFormats, uniqueFonts, uniqueFills); + cellXfs.Add(elem); + } + doc.Root.Add(cellXfs); + } - file.Content = doc; + private static void AddAlignmentElement(XElement elem, XlsxCellStyle style) + { + if(elem == null) + { + throw new ArgumentNullException(nameof(elem)); + } - return file; + if (style == null) + { + throw new ArgumentNullException(nameof(style)); } - private static void StyleAddCellXfsElement(XDocument doc, IList styles, IList uniqueBorders, IList numberFormats, IList fontInfos, IList fills) + if (style.HorizontalAlignment == HorizontalAlign.None && style.VerticalAlignment == VerticalAlign.None) { - var cellXfs = new XElement(Namespaces.workbook + "cellXfs", new XAttribute("count", styles.Count)); + return; + } - // Default Cell Style, used to make sure the row numbers don't appear broken - cellXfs.Add(new XElement(Namespaces.workbook + "xf", - new XAttribute("numFmtId", 0), - new XAttribute("fontId", 0), - new XAttribute("fillId", 0), - new XAttribute("borderId", 0), - new XAttribute("xfId", 0))); + var alignElem = new XElement(Namespaces.workbook + "alignment"); + if(style.HorizontalAlignment != HorizontalAlign.None) + { + var value = style.HorizontalAlignment switch + { + HorizontalAlign.General => "general", + HorizontalAlign.Left => "left", + HorizontalAlign.Center => "center", + HorizontalAlign.Right => "right", + HorizontalAlign.Justify => "justify", + _ => throw new InvalidOperationException("Unhandled HorizontalAlignment in Cell Style: " + style.HorizontalAlignment), + }; + alignElem.Add(new XAttribute("horizontal", value)); + } - foreach (var style in styles) + if (style.VerticalAlignment != VerticalAlign.None) + { + var value = style.VerticalAlignment switch { - var numFmtId = StandardFormatIds.TryGetValue(style.Format, out var standardNumFmtId) ? standardNumFmtId : numberFormats.IndexOf(style.Format) + CustomFormatIndex; - var fontId = fontInfos.IndexOf(style.Font); - var fillId = fills.IndexOf(style.Fill); - var borderId = uniqueBorders.IndexOf(style.Border); - var xfId = 0; - - var elem = new XElement(Namespaces.workbook + "xf", - new XAttribute("numFmtId", numFmtId), - new XAttribute("fontId", fontId), - new XAttribute("fillId", fillId), - new XAttribute("borderId", borderId), - new XAttribute("xfId", xfId)); - - if (numFmtId > 0) - { - elem.Add(new XAttribute("applyNumberFormat", 1)); - } - if (fillId > 0) - { - elem.Add(new XAttribute("applyFill", 1)); - } - if (fontId > 0) - { - elem.Add(new XAttribute("applyFont", 1)); - } - if (borderId > 0) - { - elem.Add(new XAttribute("applyBorder", 1)); - } + VerticalAlign.Top => "top", + VerticalAlign.Middle => "center", + VerticalAlign.Bottom => "bottom", + VerticalAlign.Justify => "justify", + _ => throw new InvalidOperationException("Unhandled VerticalAlignment in Cell Style: " + style.VerticalAlignment), + }; + alignElem.Add(new XAttribute("vertical", value)); + } - AddAlignmentElement(elem, style); + elem.Add(alignElem); + } - cellXfs.Add(elem); - } - doc.Root.Add(cellXfs); + private static void StyleAddCellStyleXfsElement(XDocument doc) + { + var cellStyleXfs = new XElement(Namespaces.workbook + "cellStyleXfs", new XAttribute("count", 1)); + cellStyleXfs.Add(new XElement(Namespaces.workbook + "xf", new XAttribute("numFmtId", 0), new XAttribute("fontId", 0), new XAttribute("fillId", 0), new XAttribute("borderId", 0))); + doc.Root.Add(cellStyleXfs); + } + + private static void StyleAddNumFmtsElement(XDocument doc, List numberFormats) + { + var numFmtsElem = new XElement(Namespaces.workbook + "numFmts", new XAttribute("count", numberFormats.Count)); + + for (int i = 0; i < numberFormats.Count; i++) + { + var fmtElem = new XElement(Namespaces.workbook + "numFmt", + new XAttribute("numFmtId", i + CustomFormatIndex), + new XAttribute("formatCode", numberFormats[i])); + numFmtsElem.Add(fmtElem); } + doc.Root.Add(numFmtsElem); + } - private static void AddAlignmentElement(XElement elem, XlsxCellStyle style) + private static void StyleAddBordersElement(XDocument doc, List uniqueBorders) + { + var borders = new XElement(Namespaces.workbook + "borders", new XAttribute("count", uniqueBorders.Count)); + foreach (var border in uniqueBorders) { - if(elem == null) - { - throw new ArgumentNullException(nameof(elem)); - } + var bex = new XElement(Namespaces.workbook + "border"); - if (style == null) + var left = new XElement(Namespaces.workbook + "left"); + if (border.HasFlag(CellBorder.Left)) { - throw new ArgumentNullException(nameof(style)); + left.Add(new XAttribute("style", "thin")); + left.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); } + bex.Add(left); - if (style.HorizontalAlignment == HorizontalAlign.None && style.VerticalAlignment == VerticalAlign.None) + var right = new XElement(Namespaces.workbook + "right"); + if (border.HasFlag(CellBorder.Right)) { - return; + right.Add(new XAttribute("style", "thin")); + right.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); } + bex.Add(right); - var alignElem = new XElement(Namespaces.workbook + "alignment"); - if(style.HorizontalAlignment != HorizontalAlign.None) + var top = new XElement(Namespaces.workbook + "top"); + if (border.HasFlag(CellBorder.Top)) { - var value = style.HorizontalAlignment switch - { - HorizontalAlign.General => "general", - HorizontalAlign.Left => "left", - HorizontalAlign.Center => "center", - HorizontalAlign.Right => "right", - HorizontalAlign.Justify => "justify", - _ => throw new InvalidOperationException("Unhandled HorizontalAlignment in Cell Style: " + style.HorizontalAlignment), - }; - alignElem.Add(new XAttribute("horizontal", value)); + top.Add(new XAttribute("style", "thin")); + top.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); } + bex.Add(top); - if (style.VerticalAlignment != VerticalAlign.None) + var bottom = new XElement(Namespaces.workbook + "bottom"); + if (border.HasFlag(CellBorder.Bottom)) { - var value = style.VerticalAlignment switch - { - VerticalAlign.Top => "top", - VerticalAlign.Middle => "center", - VerticalAlign.Bottom => "bottom", - VerticalAlign.Justify => "justify", - _ => throw new InvalidOperationException("Unhandled VerticalAlignment in Cell Style: " + style.VerticalAlignment), - }; - alignElem.Add(new XAttribute("vertical", value)); + bottom.Add(new XAttribute("style", "thin")); + bottom.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); } + bex.Add(bottom); - elem.Add(alignElem); - } + bex.Add(new XElement(Namespaces.workbook + "diagonal")); - private static void StyleAddCellStyleXfsElement(XDocument doc) - { - var cellStyleXfs = new XElement(Namespaces.workbook + "cellStyleXfs", new XAttribute("count", 1)); - cellStyleXfs.Add(new XElement(Namespaces.workbook + "xf", new XAttribute("numFmtId", 0), new XAttribute("fontId", 0), new XAttribute("fillId", 0), new XAttribute("borderId", 0))); - doc.Root.Add(cellStyleXfs); + borders.Add(bex); } + doc.Root.Add(borders); + } - private static void StyleAddNumFmtsElement(XDocument doc, List numberFormats) - { - var numFmtsElem = new XElement(Namespaces.workbook + "numFmts", new XAttribute("count", numberFormats.Count)); - - for (int i = 0; i < numberFormats.Count; i++) - { - var fmtElem = new XElement(Namespaces.workbook + "numFmt", - new XAttribute("numFmtId", i + CustomFormatIndex), - new XAttribute("formatCode", numberFormats[i])); - numFmtsElem.Add(fmtElem); - } - doc.Root.Add(numFmtsElem); - } + private static void StyleAddFillsElement(XDocument doc, List uniqueFills) + { + var fills = new XElement(Namespaces.workbook + "fills", new XAttribute("count", uniqueFills.Count)); - private static void StyleAddBordersElement(XDocument doc, List uniqueBorders) + foreach (var fill in uniqueFills) { - var borders = new XElement(Namespaces.workbook + "borders", new XAttribute("count", uniqueBorders.Count)); - foreach (var border in uniqueBorders) + var fe = new XElement(Namespaces.workbook + "fill"); + if (fill is PatternFill pf) { - var bex = new XElement(Namespaces.workbook + "border"); - - var left = new XElement(Namespaces.workbook + "left"); - if (border.HasFlag(CellBorder.Left)) + var pfe = new XElement(Namespaces.workbook + "patternFill"); + string patternType = "none"; + switch (pf.PatternType) { - left.Add(new XAttribute("style", "thin")); - left.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); + case PatternType.Solid: patternType = "solid"; break; + case PatternType.Gray750: patternType = "darkGray"; break; + case PatternType.Gray500: patternType = "mediumGray"; break; + case PatternType.Gray250: patternType = "lightGray"; break; + case PatternType.Gray125: patternType = "gray125"; break; + case PatternType.Gray0625: patternType = "gray0625"; break; + case PatternType.HorizontalStripe: patternType = "darkHorizontal"; break; + case PatternType.VerticalStripe: patternType = "darkVertical"; break; + case PatternType.ReverseDiagonalStripe: patternType = "darkDown"; break; + case PatternType.DiagonalStripe: patternType = "darkUp"; break; + case PatternType.DiagonalCrosshatch: patternType = "darkGrid"; break; + case PatternType.ThickDiagonalCrosshatch: patternType = "darkTrellis"; break; + case PatternType.ThinHorizontalStripe: patternType = "lightHorizontal"; break; + case PatternType.ThinVerticalStripe: patternType = "lightVertical"; break; + case PatternType.ThinReverseDiagonalStripe: patternType = "lightDown"; break; + case PatternType.ThinDiagonalStripe: patternType = "lightUp"; break; + case PatternType.ThinHorizontalCrosshatch: patternType = "lightGrid"; break; + case PatternType.ThinDiagonalCrosshatch: patternType = "lightTrellis"; break; } - bex.Add(left); + pfe.Add(new XAttribute("patternType", patternType)); - var right = new XElement(Namespaces.workbook + "right"); - if (border.HasFlag(CellBorder.Right)) + if (pf.BackgroundColor.HasValue) { - right.Add(new XAttribute("style", "thin")); - right.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); + // Not a typo. Excel calls the fgColor "Background Color" in the UI, and the bgColor "Pattern Color" + pfe.Add(new XElement(Namespaces.workbook + "fgColor", new XAttribute("rgb", pf.BackgroundColor.Value))); } - bex.Add(right); - - var top = new XElement(Namespaces.workbook + "top"); - if (border.HasFlag(CellBorder.Top)) + if (pf.PatternColor.HasValue) { - top.Add(new XAttribute("style", "thin")); - top.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); + pfe.Add(new XElement(Namespaces.workbook + "bgColor", new XAttribute("rgb", pf.PatternColor.Value))); } - bex.Add(top); - - var bottom = new XElement(Namespaces.workbook + "bottom"); - if (border.HasFlag(CellBorder.Bottom)) + else if (pf.BackgroundColor.HasValue) { - bottom.Add(new XAttribute("style", "thin")); - bottom.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); + // fgColor without bgColor causes Excel to show an error + pfe.Add(new XElement(Namespaces.workbook + "bgColor", new XAttribute("auto", 1))); } - bex.Add(bottom); - - bex.Add(new XElement(Namespaces.workbook + "diagonal")); - borders.Add(bex); + fe.Add(pfe); } - doc.Root.Add(borders); + //else if (fill is GradientFill gf) + //{ + // TODO: Not Yet Implemented + //} + fills.Add(fe); } + doc.Root.Add(fills); + } - private static void StyleAddFillsElement(XDocument doc, List uniqueFills) - { - var fills = new XElement(Namespaces.workbook + "fills", new XAttribute("count", uniqueFills.Count)); - - foreach (var fill in uniqueFills) - { - var fe = new XElement(Namespaces.workbook + "fill"); - if (fill is PatternFill pf) - { - var pfe = new XElement(Namespaces.workbook + "patternFill"); - string patternType = "none"; - switch (pf.PatternType) - { - case PatternType.Solid: patternType = "solid"; break; - case PatternType.Gray750: patternType = "darkGray"; break; - case PatternType.Gray500: patternType = "mediumGray"; break; - case PatternType.Gray250: patternType = "lightGray"; break; - case PatternType.Gray125: patternType = "gray125"; break; - case PatternType.Gray0625: patternType = "gray0625"; break; - case PatternType.HorizontalStripe: patternType = "darkHorizontal"; break; - case PatternType.VerticalStripe: patternType = "darkVertical"; break; - case PatternType.ReverseDiagonalStripe: patternType = "darkDown"; break; - case PatternType.DiagonalStripe: patternType = "darkUp"; break; - case PatternType.DiagonalCrosshatch: patternType = "darkGrid"; break; - case PatternType.ThickDiagonalCrosshatch: patternType = "darkTrellis"; break; - case PatternType.ThinHorizontalStripe: patternType = "lightHorizontal"; break; - case PatternType.ThinVerticalStripe: patternType = "lightVertical"; break; - case PatternType.ThinReverseDiagonalStripe: patternType = "lightDown"; break; - case PatternType.ThinDiagonalStripe: patternType = "lightUp"; break; - case PatternType.ThinHorizontalCrosshatch: patternType = "lightGrid"; break; - case PatternType.ThinDiagonalCrosshatch: patternType = "lightTrellis"; break; - } - pfe.Add(new XAttribute("patternType", patternType)); - - if (pf.BackgroundColor.HasValue) - { - // Not a typo. Excel calls the fgColor "Background Color" in the UI, and the bgColor "Pattern Color" - pfe.Add(new XElement(Namespaces.workbook + "fgColor", new XAttribute("rgb", pf.BackgroundColor.Value))); - } - if (pf.PatternColor.HasValue) - { - pfe.Add(new XElement(Namespaces.workbook + "bgColor", new XAttribute("rgb", pf.PatternColor.Value))); - } - else if (pf.BackgroundColor.HasValue) - { - // fgColor without bgColor causes Excel to show an error - pfe.Add(new XElement(Namespaces.workbook + "bgColor", new XAttribute("auto", 1))); - } - - fe.Add(pfe); - } - //else if (fill is GradientFill gf) - //{ - // TODO: Not Yet Implemented - //} - fills.Add(fe); - } - doc.Root.Add(fills); - } + private static void StyleAddFontsElement(XDocument doc, IList fontInfos) + { + var fonts = new XElement(Namespaces.workbook + "fonts", new XAttribute("count", fontInfos.Count)); - private static void StyleAddFontsElement(XDocument doc, IList fontInfos) + foreach (var font in fontInfos) { - var fonts = new XElement(Namespaces.workbook + "fonts", new XAttribute("count", fontInfos.Count)); + var elem = new XElement(Namespaces.workbook + "font", + new XElement(Namespaces.workbook + "sz", new XAttribute("val", font.Size)), + new XElement(Namespaces.workbook + "name", new XAttribute("val", font.Name)), + new XElement(Namespaces.workbook + "color", new XAttribute("rgb", font.TextColor)) + ); - foreach (var font in fontInfos) - { - var elem = new XElement(Namespaces.workbook + "font", - new XElement(Namespaces.workbook + "sz", new XAttribute("val", font.Size)), - new XElement(Namespaces.workbook + "name", new XAttribute("val", font.Name)), - new XElement(Namespaces.workbook + "color", new XAttribute("rgb", font.TextColor)) - ); - - if (font.Bold) { elem.Add(new XElement(Namespaces.workbook + "b")); } - if (font.Italic) { elem.Add(new XElement(Namespaces.workbook + "i")); } - if (font.Underline) { elem.Add(new XElement(Namespaces.workbook + "u")); } + if (font.Bold) { elem.Add(new XElement(Namespaces.workbook + "b")); } + if (font.Italic) { elem.Add(new XElement(Namespaces.workbook + "i")); } + if (font.Underline) { elem.Add(new XElement(Namespaces.workbook + "u")); } - fonts.Add(elem); - } - doc.Root.Add(fonts); + fonts.Add(elem); } + doc.Root.Add(fonts); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxCell.cs b/src/Simplexcel/XlsxInternal/XlsxCell.cs index 4fe81b1..b88b47d 100644 --- a/src/Simplexcel/XlsxInternal/XlsxCell.cs +++ b/src/Simplexcel/XlsxInternal/XlsxCell.cs @@ -1,28 +1,27 @@ -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// A Cell inside the sheet.xml file +/// ECMA-376, 3rd Edition, Part 1, 18.3.1.4 c (Cell) +/// +internal class XlsxCell { + /// + internal string CellType { get; set; } + /// - /// A Cell inside the sheet.xml file - /// ECMA-376, 3rd Edition, Part 1, 18.3.1.4 c (Cell) + /// r (Reference) An "A1" style reference to the location of this cell + /// The possible values for this attribute are defined by the ST_CellRef simple type (§18.18.7). /// - internal class XlsxCell - { - /// - internal string CellType { get; set; } + internal string Reference { get; set; } - /// - /// r (Reference) An "A1" style reference to the location of this cell - /// The possible values for this attribute are defined by the ST_CellRef simple type (§18.18.7). - /// - internal string Reference { get; set; } - - /// - /// s (StyleIndex) - /// - internal int StyleIndex { get; set; } + /// + /// s (StyleIndex) + /// + internal int StyleIndex { get; set; } - /// - /// The v element - /// - internal object Value { get; set; } - } -} + /// + /// The v element + /// + internal object Value { get; set; } +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs b/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs index 073c9fc..2cf4509 100644 --- a/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs +++ b/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs @@ -1,96 +1,95 @@ using System; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// The Style Information about a cell. Isolated from the Cell object because we need to compare styles when building the styles.xml file +/// +internal class XlsxCellStyle : IEquatable { - /// - /// The Style Information about a cell. Isolated from the Cell object because we need to compare styles when building the styles.xml file - /// - internal class XlsxCellStyle : IEquatable + internal XlsxCellStyle() { - internal XlsxCellStyle() - { - Font = new XlsxFont(); - Fill = new PatternFill(); - } + Font = new XlsxFont(); + Fill = new PatternFill(); + } - internal XlsxFont Font { get; set; } + internal XlsxFont Font { get; set; } - internal CellBorder Border { get; set; } + internal CellBorder Border { get; set; } - internal string Format { get; set; } + internal string Format { get; set; } - internal VerticalAlign VerticalAlignment { get; set; } + internal VerticalAlign VerticalAlignment { get; set; } - internal HorizontalAlign HorizontalAlignment { get; set; } + internal HorizontalAlign HorizontalAlignment { get; set; } - internal PatternFill Fill { get; set; } + internal PatternFill Fill { get; set; } - /// - /// Compare this to another - /// - /// - /// - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(XlsxCellStyle)) return false; - return Equals((XlsxCellStyle)obj); - } + /// + /// Compare this to another + /// + /// + /// + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(XlsxCellStyle)) return false; + return Equals((XlsxCellStyle)obj); + } - /// - /// Compare this to another - /// - /// - /// - public bool Equals(XlsxCellStyle other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; + /// + /// Compare this to another + /// + /// + /// + public bool Equals(XlsxCellStyle other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; - return Equals(other.Border, Border) - && other.Font.Equals(Font) - && other.Format.Equals(Format) - && Equals(other.VerticalAlignment, VerticalAlignment) - && Equals(other.HorizontalAlignment, HorizontalAlignment) - && Equals(other.Fill, Fill) - ; - } + return Equals(other.Border, Border) + && other.Font.Equals(Font) + && other.Format.Equals(Format) + && Equals(other.VerticalAlignment, VerticalAlignment) + && Equals(other.HorizontalAlignment, HorizontalAlignment) + && Equals(other.Fill, Fill) + ; + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - int result = Border.GetHashCode(); - result = (result * 397) ^ Font.GetHashCode(); - result = (result * 397) ^ Format.GetHashCode(); - result = (result * 397) ^ VerticalAlignment.GetHashCode(); - result = (result * 397) ^ HorizontalAlignment.GetHashCode(); - result = (result * 397) ^ Fill.GetHashCode(); - return result; - } + int result = Border.GetHashCode(); + result = (result * 397) ^ Font.GetHashCode(); + result = (result * 397) ^ Format.GetHashCode(); + result = (result * 397) ^ VerticalAlignment.GetHashCode(); + result = (result * 397) ^ HorizontalAlignment.GetHashCode(); + result = (result * 397) ^ Fill.GetHashCode(); + return result; } + } - /// - /// Compare a to another - /// - /// - /// - /// - public static bool operator ==(XlsxCellStyle left, XlsxCellStyle right) - { - return Equals(left, right); - } + /// + /// Compare a to another + /// + /// + /// + /// + public static bool operator ==(XlsxCellStyle left, XlsxCellStyle right) + { + return Equals(left, right); + } - /// - /// Compare a to another - /// - /// - /// - /// - public static bool operator !=(XlsxCellStyle left, XlsxCellStyle right) - { - return !Equals(left, right); - } + /// + /// Compare a to another + /// + /// + /// + /// + public static bool operator !=(XlsxCellStyle left, XlsxCellStyle right) + { + return !Equals(left, right); } } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxCellTypes.cs b/src/Simplexcel/XlsxInternal/XlsxCellTypes.cs index 3d8dcf5..07a8321 100644 --- a/src/Simplexcel/XlsxInternal/XlsxCellTypes.cs +++ b/src/Simplexcel/XlsxInternal/XlsxCellTypes.cs @@ -1,44 +1,43 @@ -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// ECMA-376, 3rd Edition, Part 1, 18.18.11 ST_CellType (Cell Type) +/// +internal static class XlsxCellTypes { /// - /// ECMA-376, 3rd Edition, Part 1, 18.18.11 ST_CellType (Cell Type) + /// b (Boolean) Cell containing a boolean. /// - internal static class XlsxCellTypes - { - /// - /// b (Boolean) Cell containing a boolean. - /// - internal static readonly string Boolean = "b"; + internal static readonly string Boolean = "b"; - /// - /// d (Date) Cell contains a date in the ISO 8601 format. - /// - internal static readonly string Date = "d"; + /// + /// d (Date) Cell contains a date in the ISO 8601 format. + /// + internal static readonly string Date = "d"; - /// - /// e (Error) Cell containing an error. - /// - internal static readonly string Error = "e"; + /// + /// e (Error) Cell containing an error. + /// + internal static readonly string Error = "e"; - /// - /// inlineStr (Inline String) Cell containing an (inline) rich string, i.e., one not in the shared string table. - /// Note: Excel expects "str" instead of "inlineStr" - /// - internal static readonly string InlineString = "str"; + /// + /// inlineStr (Inline String) Cell containing an (inline) rich string, i.e., one not in the shared string table. + /// Note: Excel expects "str" instead of "inlineStr" + /// + internal static readonly string InlineString = "str"; - /// - /// n (Number) Cell containing a number. - /// - internal static readonly string Number = "n"; + /// + /// n (Number) Cell containing a number. + /// + internal static readonly string Number = "n"; - /// - /// s (Shared String) Cell containing a shared string. - /// - internal static readonly string SharedString = "s"; + /// + /// s (Shared String) Cell containing a shared string. + /// + internal static readonly string SharedString = "s"; - /// - /// str (String) Cell containing a formula string. - /// - internal static readonly string FormulaString = "str"; - } + /// + /// str (String) Cell containing a formula string. + /// + internal static readonly string FormulaString = "str"; } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxExtensionMethods.cs b/src/Simplexcel/XlsxInternal/XlsxExtensionMethods.cs index 19dcea2..1122309 100644 --- a/src/Simplexcel/XlsxInternal/XlsxExtensionMethods.cs +++ b/src/Simplexcel/XlsxInternal/XlsxExtensionMethods.cs @@ -1,23 +1,22 @@ using System; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +internal static class XlsxEnumFormatter { - internal static class XlsxEnumFormatter + internal static string GetXmlValue(this Panes pane) => pane switch { - internal static string GetXmlValue(this Panes pane) => pane switch - { - Panes.BottomLeft => "bottomLeft", - Panes.BottomRight => "bottomRight", - Panes.TopLeft => "topLeft", - Panes.TopRight => "topRight", - _ => throw new ArgumentOutOfRangeException(nameof(pane), "Invalid Pane: " + pane), - }; + Panes.BottomLeft => "bottomLeft", + Panes.BottomRight => "bottomRight", + Panes.TopLeft => "topLeft", + Panes.TopRight => "topRight", + _ => throw new ArgumentOutOfRangeException(nameof(pane), "Invalid Pane: " + pane), + }; - internal static string GetXmlValue(this PaneState state) => state switch - { - PaneState.Split => "split", - PaneState.Frozen => "frozen", - PaneState.FrozenSplit => "frozenSplit", - _ => throw new ArgumentOutOfRangeException(nameof(state), "Invalid Pane State: " + state), - }; - } + internal static string GetXmlValue(this PaneState state) => state switch + { + PaneState.Split => "split", + PaneState.Frozen => "frozen", + PaneState.FrozenSplit => "frozenSplit", + _ => throw new ArgumentOutOfRangeException(nameof(state), "Invalid Pane State: " + state), + }; } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxFont.cs b/src/Simplexcel/XlsxInternal/XlsxFont.cs index 83d87ae..1337df5 100644 --- a/src/Simplexcel/XlsxInternal/XlsxFont.cs +++ b/src/Simplexcel/XlsxInternal/XlsxFont.cs @@ -1,64 +1,63 @@ using System; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +internal class XlsxFont : IEquatable { - internal class XlsxFont : IEquatable - { - internal string Name { get; set; } - internal int Size { get; set; } - internal bool Bold { get; set; } - internal bool Italic { get; set; } - internal bool Underline { get; set; } - internal Color TextColor { get; set; } + internal string Name { get; set; } + internal int Size { get; set; } + internal bool Bold { get; set; } + internal bool Italic { get; set; } + internal bool Underline { get; set; } + internal Color TextColor { get; set; } - internal XlsxFont() - { - Name = "Calibri"; - Size = 11; - } + internal XlsxFont() + { + Name = "Calibri"; + Size = 11; + } - public override bool Equals(object obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != typeof(XlsxFont)) return false; - return Equals((XlsxFont)obj); - } + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(XlsxFont)) return false; + return Equals((XlsxFont)obj); + } - public bool Equals(XlsxFont other) - { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(other.Name, Name) - && other.Size == Size - && other.Bold.Equals(Bold) - && other.Italic.Equals(Italic) - && other.Underline.Equals(Underline) - && other.TextColor.Equals(TextColor); - } + public bool Equals(XlsxFont other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(other.Name, Name) + && other.Size == Size + && other.Bold.Equals(Bold) + && other.Italic.Equals(Italic) + && other.Underline.Equals(Underline) + && other.TextColor.Equals(TextColor); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - int result = (Name != null ? Name.GetHashCode() : 0); - result = (result*397) ^ Size; - result = (result*397) ^ Bold.GetHashCode(); - result = (result*397) ^ Italic.GetHashCode(); - result = (result*397) ^ Underline.GetHashCode(); - result = (result*397) ^ TextColor.GetHashCode(); - return result; - } + int result = (Name != null ? Name.GetHashCode() : 0); + result = (result*397) ^ Size; + result = (result*397) ^ Bold.GetHashCode(); + result = (result*397) ^ Italic.GetHashCode(); + result = (result*397) ^ Underline.GetHashCode(); + result = (result*397) ^ TextColor.GetHashCode(); + return result; } + } - public static bool operator ==(XlsxFont left, XlsxFont right) - { - return Equals(left, right); - } + public static bool operator ==(XlsxFont left, XlsxFont right) + { + return Equals(left, right); + } - public static bool operator !=(XlsxFont left, XlsxFont right) - { - return !Equals(left, right); - } + public static bool operator !=(XlsxFont left, XlsxFont right) + { + return !Equals(left, right); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs b/src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs index dbb643d..e590e81 100644 --- a/src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs +++ b/src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs @@ -1,45 +1,44 @@ using System; using System.Collections.Generic; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +internal sealed class XlsxIgnoredError { - internal sealed class XlsxIgnoredError + private readonly IgnoredError _ignoredError; + internal HashSet Cells { get; } + + public XlsxIgnoredError() { - private readonly IgnoredError _ignoredError; - internal HashSet Cells { get; } + Cells = new HashSet(); + _ignoredError = new IgnoredError(); + } - public XlsxIgnoredError() + internal IgnoredError IgnoredError + { + get { - Cells = new HashSet(); - _ignoredError = new IgnoredError(); + // Note: This is a mutable reference, but changing it would be... bad. + return _ignoredError; } - - internal IgnoredError IgnoredError + set { - get + if (value == null) { - // Note: This is a mutable reference, but changing it would be... bad. - return _ignoredError; + throw new ArgumentNullException(nameof(value)); } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - // And because the reference is mutable, this stores a copy so that modifications to the Worksheet don't break this - _ignoredError.NumberStoredAsText = value.NumberStoredAsText; - IgnoredErrorId = _ignoredError.GetHashCode(); - } + // And because the reference is mutable, this stores a copy so that modifications to the Worksheet don't break this + _ignoredError.NumberStoredAsText = value.NumberStoredAsText; + IgnoredErrorId = _ignoredError.GetHashCode(); } + } - internal int IgnoredErrorId { get; private set; } + internal int IgnoredErrorId { get; private set; } - internal string GetSqRef() - { - var ranges = CellAddressHelper.DetermineRanges(Cells); - return string.Join(" ", ranges); - } + internal string GetSqRef() + { + var ranges = CellAddressHelper.DetermineRanges(Cells); + return string.Join(" ", ranges); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs b/src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs index f70db13..94177fa 100644 --- a/src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs +++ b/src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs @@ -1,40 +1,39 @@ using System.Collections.Generic; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +internal sealed class XlsxIgnoredErrorCollection { - internal sealed class XlsxIgnoredErrorCollection + internal IList DistinctIgnoredErrors { get; } + + public XlsxIgnoredErrorCollection() { - internal IList DistinctIgnoredErrors { get; } + DistinctIgnoredErrors = new List(); + } - public XlsxIgnoredErrorCollection() + public void AddIgnoredError(CellAddress cellAddress, IgnoredError ignoredErrors) + { + if (!ignoredErrors.IsDifferentFromDefault) { - DistinctIgnoredErrors = new List(); + return; } - public void AddIgnoredError(CellAddress cellAddress, IgnoredError ignoredErrors) + var id = ignoredErrors.GetHashCode(); + + foreach (var distinctIgnored in DistinctIgnoredErrors) { - if (!ignoredErrors.IsDifferentFromDefault) + if (distinctIgnored.IgnoredErrorId == id) { + distinctIgnored.Cells.Add(cellAddress); return; } - - var id = ignoredErrors.GetHashCode(); - - foreach (var distinctIgnored in DistinctIgnoredErrors) - { - if (distinctIgnored.IgnoredErrorId == id) - { - distinctIgnored.Cells.Add(cellAddress); - return; - } - } - - var newIgnoredError = new XlsxIgnoredError - { - IgnoredError = ignoredErrors - }; - newIgnoredError.Cells.Add(cellAddress); - DistinctIgnoredErrors.Add(newIgnoredError); } + + var newIgnoredError = new XlsxIgnoredError + { + IgnoredError = ignoredErrors + }; + newIgnoredError.Cells.Add(cellAddress); + DistinctIgnoredErrors.Add(newIgnoredError); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxPackage.cs b/src/Simplexcel/XlsxInternal/XlsxPackage.cs index 6986f5d..9e63e0f 100644 --- a/src/Simplexcel/XlsxInternal/XlsxPackage.cs +++ b/src/Simplexcel/XlsxInternal/XlsxPackage.cs @@ -4,116 +4,115 @@ using System.Linq; using System.Xml.Linq; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// A Wrapper for an .xlsx file, containing all the contents and relations and logic to create the file from that content +/// +internal class XlsxPackage { /// - /// A Wrapper for an .xlsx file, containing all the contents and relations and logic to create the file from that content + /// All XML Files in this package /// - internal class XlsxPackage - { - /// - /// All XML Files in this package - /// - internal IList XmlFiles { get; } + internal IList XmlFiles { get; } - /// - /// Package-level relationships (/_rels/.rels) - /// - internal IList Relationships { get; } + /// + /// Package-level relationships (/_rels/.rels) + /// + internal IList Relationships { get; } + + /// + /// Workbook-level relationships (/xl/_rels/workbook.xml.rels) + /// + internal IList WorkbookRelationships { get; } - /// - /// Workbook-level relationships (/xl/_rels/workbook.xml.rels) - /// - internal IList WorkbookRelationships { get; } + internal XlsxPackage() + { + Relationships = new List(); + WorkbookRelationships = new List(); + XmlFiles = new List(); + } - internal XlsxPackage() + /// + /// Save the Xlsx Package to a new Stream (that the caller owns and has to dispose) + /// + /// + internal void SaveToStream(Stream outputStream, bool compress) + { + if(outputStream == null || !outputStream.CanWrite || !outputStream.CanSeek) { - Relationships = new List(); - WorkbookRelationships = new List(); - XmlFiles = new List(); + throw new InvalidOperationException("Stream to save to must be writeable and seekable."); } - /// - /// Save the Xlsx Package to a new Stream (that the caller owns and has to dispose) - /// - /// - internal void SaveToStream(Stream outputStream, bool compress) + using (var pkg = new ZipPackage(outputStream, compress)) { - if(outputStream == null || !outputStream.CanWrite || !outputStream.CanSeek) - { - throw new InvalidOperationException("Stream to save to must be writeable and seekable."); - } + WriteInfoXmlFile(pkg); - using (var pkg = new ZipPackage(outputStream, compress)) + foreach (var file in XmlFiles) { - WriteInfoXmlFile(pkg); + pkg.WriteXmlFile(file); - foreach (var file in XmlFiles) + var relations = Relationships.Where(r => r.Target == file); + foreach (var rel in relations) { - pkg.WriteXmlFile(file); - - var relations = Relationships.Where(r => r.Target == file); - foreach (var rel in relations) - { - pkg.AddRelationship(rel); - } + pkg.AddRelationship(rel); } + } - if (WorkbookRelationships.Count > 0) - { - pkg.WriteXmlFile(WorkbookRelsXml()); - } + if (WorkbookRelationships.Count > 0) + { + pkg.WriteXmlFile(WorkbookRelsXml()); } - outputStream.Seek(0, SeekOrigin.Begin); } + outputStream.Seek(0, SeekOrigin.Begin); + } - private void WriteInfoXmlFile(ZipPackage pkg) + private void WriteInfoXmlFile(ZipPackage pkg) + { + var infoXml = new XmlFile { - var infoXml = new XmlFile - { - Path = "simplexcel.xml", - Content = new XDocument(new XElement(Namespaces.simplexcel + "docInfo", new XAttribute("xmlns", Namespaces.simplexcel))) - }; + Path = "simplexcel.xml", + Content = new XDocument(new XElement(Namespaces.simplexcel + "docInfo", new XAttribute("xmlns", Namespaces.simplexcel))) + }; - infoXml.Content.Root.Add(new XElement(Namespaces.simplexcel + "version", - new XAttribute("major", SimplexcelVersion.Version.Major), - new XAttribute("minor", SimplexcelVersion.Version.Minor), - new XAttribute("build", SimplexcelVersion.Version.Build), - new XAttribute("revision", SimplexcelVersion.Version.Revision), - new XText(SimplexcelVersion.VersionString) - )); + infoXml.Content.Root.Add(new XElement(Namespaces.simplexcel + "version", + new XAttribute("major", SimplexcelVersion.Version.Major), + new XAttribute("minor", SimplexcelVersion.Version.Minor), + new XAttribute("build", SimplexcelVersion.Version.Build), + new XAttribute("revision", SimplexcelVersion.Version.Revision), + new XText(SimplexcelVersion.VersionString) + )); - infoXml.Content.Root.Add(new XElement(Namespaces.simplexcel + "created", DateTime.UtcNow)); + infoXml.Content.Root.Add(new XElement(Namespaces.simplexcel + "created", DateTime.UtcNow)); - pkg.WriteXmlFile(infoXml); - } + pkg.WriteXmlFile(infoXml); + } - /// - /// Create the xl/_rels/workbook.xml.rels file - /// - /// - internal XmlFile WorkbookRelsXml() + /// + /// Create the xl/_rels/workbook.xml.rels file + /// + /// + internal XmlFile WorkbookRelsXml() + { + var file = new XmlFile { - var file = new XmlFile - { - ContentType = "application/vnd.openxmlformats-package.relationships+xml", - Path = "xl/_rels/workbook.xml.rels" - }; - - var content = new XDocument(new XElement(Namespaces.relationship + "Relationships", new XAttribute("xmlns", Namespaces.relationship))); - foreach (var rel in WorkbookRelationships) - { - var elem = new XElement(Namespaces.relationship + "Relationship", - new XAttribute("Target", "/" + rel.Target.Path), - new XAttribute("Type", rel.Type), - new XAttribute("Id", rel.Id)); + ContentType = "application/vnd.openxmlformats-package.relationships+xml", + Path = "xl/_rels/workbook.xml.rels" + }; - content.Root.Add(elem); - } - file.Content = content; + var content = new XDocument(new XElement(Namespaces.relationship + "Relationships", new XAttribute("xmlns", Namespaces.relationship))); + foreach (var rel in WorkbookRelationships) + { + var elem = new XElement(Namespaces.relationship + "Relationship", + new XAttribute("Target", "/" + rel.Target.Path), + new XAttribute("Type", rel.Type), + new XAttribute("Id", rel.Id)); - return file; + content.Root.Add(elem); } + file.Content = content; + + return file; } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxRow.cs b/src/Simplexcel/XlsxInternal/XlsxRow.cs index cb1ff6e..5bdc1c8 100644 --- a/src/Simplexcel/XlsxInternal/XlsxRow.cs +++ b/src/Simplexcel/XlsxInternal/XlsxRow.cs @@ -1,30 +1,29 @@ using System.Collections.Generic; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// ECMA-376, 3rd Edition, Part 1, 18.3.1.73 row (Row) +/// +internal class XlsxRow { /// - /// ECMA-376, 3rd Edition, Part 1, 18.3.1.73 row (Row) + /// The c elements /// - internal class XlsxRow - { - /// - /// The c elements - /// - internal IList Cells { get; set; } + internal IList Cells { get; set; } - /// - /// This implicitly sets customHeight to 1 and ht to the value - /// - internal double? CustomRowHeight { get; set; } + /// + /// This implicitly sets customHeight to 1 and ht to the value + /// + internal double? CustomRowHeight { get; set; } - /// - /// r (Row Index) Row index. Indicates to which row in the sheet this row definition corresponds. - /// - internal int RowIndex { get; set; } + /// + /// r (Row Index) Row index. Indicates to which row in the sheet this row definition corresponds. + /// + internal int RowIndex { get; set; } - public XlsxRow() - { - Cells = new List(); - } + public XlsxRow() + { + Cells = new List(); } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxWriter.cs b/src/Simplexcel/XlsxInternal/XlsxWriter.cs index 54643d6..006939b 100644 --- a/src/Simplexcel/XlsxInternal/XlsxWriter.cs +++ b/src/Simplexcel/XlsxInternal/XlsxWriter.cs @@ -4,666 +4,665 @@ using System.Linq; using System.Xml.Linq; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +internal static class XlsxWriter { - internal static class XlsxWriter + /// + /// Save a workbook to a MemoryStream + /// + /// Thrown if there are no sheets in the workbook. + /// + internal static void Save(Workbook workbook, Stream outputStream, bool compress) { - /// - /// Save a workbook to a MemoryStream - /// - /// Thrown if there are no sheets in the workbook. - /// + if (workbook.SheetCount == 0) + { + throw new InvalidOperationException("You are trying to save a Workbook that does not contain any Worksheets."); + } + + XlsxWriterInternal.Save(workbook, outputStream, compress); + } + + /// + /// This does the actual writing by manually creating the XML according to ECMA-376, 3rd Edition, Part 1's SpreadsheetML. + /// + /// Note that in many cases, the order of elements in an XML file matters! + /// + private static class XlsxWriterInternal + { + // For some reason, Excel interprets column widths as the width minus this factor + private const decimal ExcelColumnWidthDifference = 0.7109375m; + internal static void Save(Workbook workbook, Stream outputStream, bool compress) { - if (workbook.SheetCount == 0) + var relationshipCounter = new RelationshipCounter(); + var package = new XlsxPackage(); + + // Must be done before calling GetXlsxStyles as it may add styles + foreach (var sheet in workbook.Sheets) { - throw new InvalidOperationException("You are trying to save a Workbook that does not contain any Worksheets."); + HandleLargeNumbers(sheet); } - XlsxWriterInternal.Save(workbook, outputStream, compress); - } + ExtractWorkbookSpecialXmlParts(workbook, out var styles, out var ignoredErrors); + var sharedStrings = new SharedStrings(); - /// - /// This does the actual writing by manually creating the XML according to ECMA-376, 3rd Edition, Part 1's SpreadsheetML. - /// - /// Note that in many cases, the order of elements in an XML file matters! - /// - private static class XlsxWriterInternal - { - // For some reason, Excel interprets column widths as the width minus this factor - private const decimal ExcelColumnWidthDifference = 0.7109375m; + // docProps/core.xml + var cp = CreateCoreFileProperties(workbook, relationshipCounter); + package.XmlFiles.Add(cp.Target); + package.Relationships.Add(cp); - internal static void Save(Workbook workbook, Stream outputStream, bool compress) + // xl/styles.xml + var stylesXml = StyleWriter.CreateStyleXml(styles); + var stylesRel = new Relationship(relationshipCounter) { - var relationshipCounter = new RelationshipCounter(); - var package = new XlsxPackage(); - - // Must be done before calling GetXlsxStyles as it may add styles - foreach (var sheet in workbook.Sheets) + Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", + Target = stylesXml + }; + package.XmlFiles.Add(stylesXml); + package.WorkbookRelationships.Add(stylesRel); + + // xl/worksheets/sheetX.xml + var sheetinfos = new List(); + int i = 0; + foreach (var sheet in workbook.Sheets) + { + i++; + var rel = CreateSheetFile(sheet, i, relationshipCounter, styles, ignoredErrors[sheet], sharedStrings, out XmlFile sheetRels); + if (sheetRels != null) { - HandleLargeNumbers(sheet); + package.XmlFiles.Add(sheetRels); } - ExtractWorkbookSpecialXmlParts(workbook, out var styles, out var ignoredErrors); - var sharedStrings = new SharedStrings(); - - // docProps/core.xml - var cp = CreateCoreFileProperties(workbook, relationshipCounter); - package.XmlFiles.Add(cp.Target); - package.Relationships.Add(cp); + package.XmlFiles.Add(rel.Target); + package.WorkbookRelationships.Add(rel); - // xl/styles.xml - var stylesXml = StyleWriter.CreateStyleXml(styles); - var stylesRel = new Relationship(relationshipCounter) + var sheetinfo = new SheetPackageInfo { - Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", - Target = stylesXml + RelationshipId = rel.Id, + SheetId = i, + SheetName = sheet.Name }; - package.XmlFiles.Add(stylesXml); - package.WorkbookRelationships.Add(stylesRel); - - // xl/worksheets/sheetX.xml - var sheetinfos = new List(); - int i = 0; - foreach (var sheet in workbook.Sheets) + if (sheet.PageSetup.PrintRepeatColumns > 0) { - i++; - var rel = CreateSheetFile(sheet, i, relationshipCounter, styles, ignoredErrors[sheet], sharedStrings, out XmlFile sheetRels); - if (sheetRels != null) - { - package.XmlFiles.Add(sheetRels); - } - - package.XmlFiles.Add(rel.Target); - package.WorkbookRelationships.Add(rel); - - var sheetinfo = new SheetPackageInfo - { - RelationshipId = rel.Id, - SheetId = i, - SheetName = sheet.Name - }; - if (sheet.PageSetup.PrintRepeatColumns > 0) - { - sheetinfo.RepeatCols = "'" + sheet.Name + "'!$A:$" + CellAddressHelper.ColToReference(sheet.PageSetup.PrintRepeatColumns - 1); - } - if (sheet.PageSetup.PrintRepeatRows > 0) - { - sheetinfo.RepeatRows = "'" + sheet.Name + "'!$1:$" + sheet.PageSetup.PrintRepeatRows; - } - - sheetinfos.Add(sheetinfo); + sheetinfo.RepeatCols = "'" + sheet.Name + "'!$A:$" + CellAddressHelper.ColToReference(sheet.PageSetup.PrintRepeatColumns - 1); } - - // xl/sharedStrings.xml - if (sharedStrings.Count > 0) + if (sheet.PageSetup.PrintRepeatRows > 0) { - var ssx = sharedStrings.ToRelationship(relationshipCounter); - package.XmlFiles.Add(ssx.Target); - package.WorkbookRelationships.Add(ssx); + sheetinfo.RepeatRows = "'" + sheet.Name + "'!$1:$" + sheet.PageSetup.PrintRepeatRows; } - // xl/workbook.xml - var wb = CreateWorkbookFile(sheetinfos, relationshipCounter); - package.XmlFiles.Add(wb.Target); - package.Relationships.Add(wb); - - // xl/_rels/workbook.xml.rels - package.SaveToStream(outputStream, compress); + sheetinfos.Add(sheetinfo); } - /// - /// Extract certain parts of the workbook that need special handling. - /// - /// - /// I don't want to loop over cells over and over again, so this is a way to run over all cells once and extract everything needed. - /// - /// - /// All unique Styles throughout the workbook - /// Any ignored errors. Key is the Worksheet, and the inner dictionary key is the cell range - private static void ExtractWorkbookSpecialXmlParts(Workbook workbook, out IList styles, out IDictionary ignoredErrors) + // xl/sharedStrings.xml + if (sharedStrings.Count > 0) { - styles = new List(); - ignoredErrors = new Dictionary(); - - foreach (var sheet in workbook.Sheets) - { - if (!ignoredErrors.ContainsKey(sheet)) - { - ignoredErrors[sheet] = new XlsxIgnoredErrorCollection(); - } - var sie = ignoredErrors[sheet]; - - foreach (var cpair in sheet.Cells) - { - var cell = cpair.Value; - - if (!styles.Contains(cell.XlsxCellStyle)) - { - styles.Add(cell.XlsxCellStyle); - } - - sie.AddIgnoredError(cpair.Key, cell.IgnoredErrors); - } - } + var ssx = sharedStrings.ToRelationship(relationshipCounter); + package.XmlFiles.Add(ssx.Target); + package.WorkbookRelationships.Add(ssx); } - /// - /// Generated the docProps/core.xml which contains author, creation date etc. - /// - /// - private static Relationship CreateCoreFileProperties(Workbook workbook, RelationshipCounter relationshipCounter) - { - var file = new XmlFile - { - ContentType = "application/vnd.openxmlformats-package.core-properties+xml", - Path = "docProps/core.xml" - }; + // xl/workbook.xml + var wb = CreateWorkbookFile(sheetinfos, relationshipCounter); + package.XmlFiles.Add(wb.Target); + package.Relationships.Add(wb); - var dc = Namespaces.dc; - var dcterms = Namespaces.dcterms; - var xsi = Namespaces.xsi; - var cp = Namespaces.coreProperties; + // xl/_rels/workbook.xml.rels + package.SaveToStream(outputStream, compress); + } - var doc = new XDocument(); - var root = new XElement(cp + "coreProperties", - new XAttribute(XNamespace.Xmlns + "cp", cp), - new XAttribute(XNamespace.Xmlns + "dc", dc), - new XAttribute(XNamespace.Xmlns + "dcterms", dcterms), - new XAttribute(XNamespace.Xmlns + "xsi", xsi) - ); + /// + /// Extract certain parts of the workbook that need special handling. + /// + /// + /// I don't want to loop over cells over and over again, so this is a way to run over all cells once and extract everything needed. + /// + /// + /// All unique Styles throughout the workbook + /// Any ignored errors. Key is the Worksheet, and the inner dictionary key is the cell range + private static void ExtractWorkbookSpecialXmlParts(Workbook workbook, out IList styles, out IDictionary ignoredErrors) + { + styles = new List(); + ignoredErrors = new Dictionary(); - if (!string.IsNullOrEmpty(workbook.Title)) - { - root.Add(new XElement(dc + "title", workbook.Title)); - } - if (!string.IsNullOrEmpty(workbook.Author)) + foreach (var sheet in workbook.Sheets) + { + if (!ignoredErrors.ContainsKey(sheet)) { - root.Add(new XElement(dc + "creator", workbook.Author)); - root.Add(new XElement(cp + "lastModifiedBy", workbook.Author)); + ignoredErrors[sheet] = new XlsxIgnoredErrorCollection(); } + var sie = ignoredErrors[sheet]; - root.Add(new XElement(dcterms + "created", DateTime.UtcNow, new XAttribute(xsi + "type", "dcterms:W3CDTF"))); - root.Add(new XElement(dcterms + "modified", DateTime.UtcNow, new XAttribute(xsi + "type", "dcterms:W3CDTF"))); - - - doc.Add(root); - - file.Content = doc; - - var rel = new Relationship(relationshipCounter) + foreach (var cpair in sheet.Cells) { - Target = file, - Type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" - }; + var cell = cpair.Value; - return rel; + if (!styles.Contains(cell.XlsxCellStyle)) + { + styles.Add(cell.XlsxCellStyle); + } + + sie.AddIgnoredError(cpair.Key, cell.IgnoredErrors); + } } + } - /// - /// Create the xl/workbook.xml file and associated relationship - /// - /// - /// - /// - private static Relationship CreateWorkbookFile(List sheetInfos, RelationshipCounter relationshipCounter) + /// + /// Generated the docProps/core.xml which contains author, creation date etc. + /// + /// + private static Relationship CreateCoreFileProperties(Workbook workbook, RelationshipCounter relationshipCounter) + { + var file = new XmlFile { - var file = new XmlFile - { - ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", - Path = "xl/workbook.xml" - }; + ContentType = "application/vnd.openxmlformats-package.core-properties+xml", + Path = "docProps/core.xml" + }; + + var dc = Namespaces.dc; + var dcterms = Namespaces.dcterms; + var xsi = Namespaces.xsi; + var cp = Namespaces.coreProperties; + + var doc = new XDocument(); + var root = new XElement(cp + "coreProperties", + new XAttribute(XNamespace.Xmlns + "cp", cp), + new XAttribute(XNamespace.Xmlns + "dc", dc), + new XAttribute(XNamespace.Xmlns + "dcterms", dcterms), + new XAttribute(XNamespace.Xmlns + "xsi", xsi) + ); + + if (!string.IsNullOrEmpty(workbook.Title)) + { + root.Add(new XElement(dc + "title", workbook.Title)); + } + if (!string.IsNullOrEmpty(workbook.Author)) + { + root.Add(new XElement(dc + "creator", workbook.Author)); + root.Add(new XElement(cp + "lastModifiedBy", workbook.Author)); + } - var doc = new XDocument(new XElement(Namespaces.workbook + "workbook", - new XAttribute("xmlns", Namespaces.workbook), - new XAttribute(XNamespace.Xmlns + "r", Namespaces.workbookRelationship) - )); + root.Add(new XElement(dcterms + "created", DateTime.UtcNow, new XAttribute(xsi + "type", "dcterms:W3CDTF"))); + root.Add(new XElement(dcterms + "modified", DateTime.UtcNow, new XAttribute(xsi + "type", "dcterms:W3CDTF"))); - var sheets = new XElement(Namespaces.workbook + "sheets"); - foreach (var si in sheetInfos) - { - sheets.Add(new XElement(Namespaces.workbook + "sheet", - new XAttribute("name", si.SheetName), - new XAttribute("sheetId", si.SheetId), - new XAttribute(Namespaces.workbookRelationship + "id", si.RelationshipId) - )); - } - doc.Root.Add(sheets); + doc.Add(root); - var repeatInfos = sheetInfos.Where(si => !string.IsNullOrEmpty(si.RepeatRows) || !string.IsNullOrEmpty(si.RepeatCols)).OrderBy(si => si.SheetId).ToList(); - if (repeatInfos.Count > 0) - { - var dne = new XElement(Namespaces.workbook + "definedNames"); - foreach (var re in repeatInfos) - { - var de = new XElement(Namespaces.workbook + "definedName", - new XAttribute("name", "_xlnm.Print_Titles"), - new XAttribute("localSheetId", re.SheetId - 1) - ); + file.Content = doc; - if (!string.IsNullOrEmpty(re.RepeatCols) && !string.IsNullOrEmpty(re.RepeatRows)) - { - de.Add(new XText(re.RepeatCols + "," + re.RepeatRows)); - } - else if (!string.IsNullOrEmpty(re.RepeatCols)) - { - de.Add(new XText(re.RepeatCols)); - } - else if (!string.IsNullOrEmpty(re.RepeatRows)) - { - de.Add(new XText(re.RepeatRows)); - } + var rel = new Relationship(relationshipCounter) + { + Target = file, + Type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" + }; - dne.Add(de); - } - doc.Root.Add(dne); - } + return rel; + } - file.Content = doc; + /// + /// Create the xl/workbook.xml file and associated relationship + /// + /// + /// + /// + private static Relationship CreateWorkbookFile(List sheetInfos, RelationshipCounter relationshipCounter) + { + var file = new XmlFile + { + ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", + Path = "xl/workbook.xml" + }; - var rel = new Relationship(relationshipCounter) - { - Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", - Target = file - }; + var doc = new XDocument(new XElement(Namespaces.workbook + "workbook", + new XAttribute("xmlns", Namespaces.workbook), + new XAttribute(XNamespace.Xmlns + "r", Namespaces.workbookRelationship) + )); - return rel; + var sheets = new XElement(Namespaces.workbook + "sheets"); + foreach (var si in sheetInfos) + { + sheets.Add(new XElement(Namespaces.workbook + "sheet", + new XAttribute("name", si.SheetName), + new XAttribute("sheetId", si.SheetId), + new XAttribute(Namespaces.workbookRelationship + "id", si.RelationshipId) + )); } - private static void WriteSheetViews(Worksheet sheet, XDocument doc) - { - var sviews = sheet.GetSheetViews(); - if (sviews == null || sviews.Count == 0) { return; } + doc.Root.Add(sheets); - var sheetViews = new XElement(Namespaces.workbook + "sheetViews"); - foreach (var sv in sviews) + var repeatInfos = sheetInfos.Where(si => !string.IsNullOrEmpty(si.RepeatRows) || !string.IsNullOrEmpty(si.RepeatCols)).OrderBy(si => si.SheetId).ToList(); + if (repeatInfos.Count > 0) + { + var dne = new XElement(Namespaces.workbook + "definedNames"); + foreach (var re in repeatInfos) { - var sheetView = new XElement(Namespaces.workbook + "sheetView"); - if (sv.ShowRuler.HasValue) + var de = new XElement(Namespaces.workbook + "definedName", + new XAttribute("name", "_xlnm.Print_Titles"), + new XAttribute("localSheetId", re.SheetId - 1) + ); + + if (!string.IsNullOrEmpty(re.RepeatCols) && !string.IsNullOrEmpty(re.RepeatRows)) { - sheetView.Add(new XAttribute("showRuler", sv.ShowRuler.Value ? "1" : "0")); + de.Add(new XText(re.RepeatCols + "," + re.RepeatRows)); } - if (sv.TabSelected.HasValue) + else if (!string.IsNullOrEmpty(re.RepeatCols)) { - sheetView.Add(new XAttribute("tabSelected", sv.TabSelected.Value ? "1" : "0")); + de.Add(new XText(re.RepeatCols)); } - // TODO: Consider adding a element to the workbook - sheetView.Add(new XAttribute("workbookViewId", sv.WorkbookViewId)); - - if (sv.Pane != null) + else if (!string.IsNullOrEmpty(re.RepeatRows)) { - var p = sv.Pane; - var paneElem = new XElement(Namespaces.workbook + "pane"); - if (p.XSplit.HasValue && p.XSplit.Value != Pane.DefaultXSplit) - { - paneElem.Add(new XAttribute("xSplit", p.XSplit.Value)); - } - if (p.YSplit.HasValue && p.YSplit.Value != Pane.DefaultYSplit) - { - paneElem.Add(new XAttribute("ySplit", p.YSplit.Value)); - } - if (p.ActivePane.HasValue) - { - paneElem.Add(new XAttribute("activePane", p.ActivePane.Value.GetXmlValue())); - } - if (!string.IsNullOrEmpty(p.TopLeftCell)) - { - paneElem.Add(new XAttribute("topLeftCell", p.TopLeftCell)); - } - if (p.State.HasValue) - { - paneElem.Add(new XAttribute("state", p.State.Value.GetXmlValue())); - } - sheetView.Add(paneElem); + de.Add(new XText(re.RepeatRows)); } - foreach (var sel in sv.Selections ?? Enumerable.Empty()) - { - var selElem = new XElement(Namespaces.workbook + "selection"); - if (!string.IsNullOrEmpty(sel.ActiveCell)) - { - selElem.Add(new XAttribute("activeCell", sel.ActiveCell)); - } - selElem.Add(new XAttribute("pane", sel.ActivePane.GetXmlValue())); - sheetView.Add(selElem); - } - sheetViews.Add(sheetView); + dne.Add(de); } - - doc.Root.Add(sheetViews); + doc.Root.Add(dne); } - private static void WritePageBreaks(Worksheet sheet, XDocument doc) + file.Content = doc; + + var rel = new Relationship(relationshipCounter) { - static XElement BreakToXml(PageBreak brk) + Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", + Target = file + }; + + return rel; + } + + private static void WriteSheetViews(Worksheet sheet, XDocument doc) + { + var sviews = sheet.GetSheetViews(); + if (sviews == null || sviews.Count == 0) { return; } + + var sheetViews = new XElement(Namespaces.workbook + "sheetViews"); + foreach (var sv in sviews) + { + var sheetView = new XElement(Namespaces.workbook + "sheetView"); + if (sv.ShowRuler.HasValue) { - var elem = new XElement(Namespaces.workbook + "brk"); - elem.Add(new XAttribute("id", brk.Id)); - if (brk.IsManualBreak) + sheetView.Add(new XAttribute("showRuler", sv.ShowRuler.Value ? "1" : "0")); + } + if (sv.TabSelected.HasValue) + { + sheetView.Add(new XAttribute("tabSelected", sv.TabSelected.Value ? "1" : "0")); + } + // TODO: Consider adding a element to the workbook + sheetView.Add(new XAttribute("workbookViewId", sv.WorkbookViewId)); + + if (sv.Pane != null) + { + var p = sv.Pane; + var paneElem = new XElement(Namespaces.workbook + "pane"); + if (p.XSplit.HasValue && p.XSplit.Value != Pane.DefaultXSplit) + { + paneElem.Add(new XAttribute("xSplit", p.XSplit.Value)); + } + if (p.YSplit.HasValue && p.YSplit.Value != Pane.DefaultYSplit) { - elem.Add(new XAttribute("man", "1")); + paneElem.Add(new XAttribute("ySplit", p.YSplit.Value)); } - if (brk.IsPivotCreatedPageBreak) + if (p.ActivePane.HasValue) { - elem.Add(new XAttribute("pt", "1")); + paneElem.Add(new XAttribute("activePane", p.ActivePane.Value.GetXmlValue())); } - if (brk.Min > 0) + if (!string.IsNullOrEmpty(p.TopLeftCell)) { - elem.Add(new XAttribute("min", brk.Min)); + paneElem.Add(new XAttribute("topLeftCell", p.TopLeftCell)); } - if (brk.Max > 0) + if (p.State.HasValue) { - elem.Add(new XAttribute("max", brk.Max)); + paneElem.Add(new XAttribute("state", p.State.Value.GetXmlValue())); } - return elem; + sheetView.Add(paneElem); } - static XElement PageBreakCollectionToXml(string elementName, ICollection breaks) + foreach (var sel in sv.Selections ?? Enumerable.Empty()) { - var elem = new XElement(Namespaces.workbook + elementName); - elem.Add(new XAttribute("count", breaks.Count)); - elem.Add(new XAttribute("manualBreakCount", breaks.Count(r => r.IsManualBreak))); - foreach (var brk in breaks) + var selElem = new XElement(Namespaces.workbook + "selection"); + if (!string.IsNullOrEmpty(sel.ActiveCell)) { - elem.Add(BreakToXml(brk)); + selElem.Add(new XAttribute("activeCell", sel.ActiveCell)); } - return elem; + selElem.Add(new XAttribute("pane", sel.ActivePane.GetXmlValue())); + sheetView.Add(selElem); } + sheetViews.Add(sheetView); + } - var rowBreaks = sheet.GetRowBreaks(); - if (rowBreaks != null && rowBreaks.Count > 0) + doc.Root.Add(sheetViews); + } + + private static void WritePageBreaks(Worksheet sheet, XDocument doc) + { + static XElement BreakToXml(PageBreak brk) + { + var elem = new XElement(Namespaces.workbook + "brk"); + elem.Add(new XAttribute("id", brk.Id)); + if (brk.IsManualBreak) { - var rowBreaksElem = PageBreakCollectionToXml("rowBreaks", rowBreaks); - doc.Root.Add(rowBreaksElem); + elem.Add(new XAttribute("man", "1")); } - - var colBreaks = sheet.GetColumnBreaks(); - if (colBreaks != null && colBreaks.Count > 0) + if (brk.IsPivotCreatedPageBreak) { - var colBreaksElem = PageBreakCollectionToXml("colBreaks", colBreaks); - doc.Root.Add(colBreaksElem); + elem.Add(new XAttribute("pt", "1")); } - } - - private static void HandleLargeNumbers(Worksheet sheet) - { - if(sheet == null) + if (brk.Min > 0) { - throw new ArgumentNullException(nameof(sheet)); + elem.Add(new XAttribute("min", brk.Min)); } - - if (sheet.LargeNumberHandlingMode == LargeNumberHandlingMode.None) + if (brk.Max > 0) { - return; + elem.Add(new XAttribute("max", brk.Max)); } + return elem; + } - if (sheet.LargeNumberHandlingMode == LargeNumberHandlingMode.StoreAsText) + static XElement PageBreakCollectionToXml(string elementName, ICollection breaks) + { + var elem = new XElement(Namespaces.workbook + elementName); + elem.Add(new XAttribute("count", breaks.Count)); + elem.Add(new XAttribute("manualBreakCount", breaks.Count(r => r.IsManualBreak))); + foreach (var brk in breaks) { - foreach (var cellPair in sheet.Cells) - { - if (cellPair.Value.CellType != CellType.Number) { continue; } - var cell = cellPair.Value; - var numVal = (Decimal)cell.Value; - if (!Cell.IsLargeNumber(numVal)) { continue; } - - cell.Format = BuiltInCellFormat.General; - if (cell.HorizontalAlignment == HorizontalAlign.None) - { - cell.HorizontalAlignment = HorizontalAlign.Right; - } - cell.IgnoredErrors.NumberStoredAsText = true; - } - return; + elem.Add(BreakToXml(brk)); } - - throw new InvalidOperationException($"Unhandled LargeNumberHandlingMode in sheet {sheet.Name}: {sheet.LargeNumberHandlingMode}"); + return elem; } - /// - /// Create the xl/worksheets/sheetX.xml file - /// - /// - /// - /// - /// - /// - /// - /// If this worksheet needs an xl/worksheets/_rels/sheetX.xml.rels file - /// - private static Relationship CreateSheetFile(Worksheet sheet, int sheetIndex, RelationshipCounter relationshipCounter, IList styles, XlsxIgnoredErrorCollection ignoredErrors, SharedStrings sharedStrings, out XmlFile sheetRels) + var rowBreaks = sheet.GetRowBreaks(); + if (rowBreaks != null && rowBreaks.Count > 0) { - var rows = GetXlsxRows(sheet, styles, sharedStrings); - - var file = new XmlFile - { - ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml", - Path = "xl/worksheets/sheet" + sheetIndex + ".xml" - }; + var rowBreaksElem = PageBreakCollectionToXml("rowBreaks", rowBreaks); + doc.Root.Add(rowBreaksElem); + } - var doc = new XDocument(new XElement(Namespaces.workbook + "worksheet", - new XAttribute("xmlns", Namespaces.workbook), - new XAttribute(XNamespace.Xmlns + "r", Namespaces.relationship), - new XAttribute(XNamespace.Xmlns + "mc", Namespaces.mc), - new XAttribute(XNamespace.Xmlns + "x14ac", Namespaces.x14ac), - new XAttribute(XNamespace.Xmlns + "or", Namespaces.officeRelationships), - new XAttribute(Namespaces.mc + "Ignorable", "x14ac") - )); + var colBreaks = sheet.GetColumnBreaks(); + if (colBreaks != null && colBreaks.Count > 0) + { + var colBreaksElem = PageBreakCollectionToXml("colBreaks", colBreaks); + doc.Root.Add(colBreaksElem); + } + } - WriteSheetViews(sheet, doc); + private static void HandleLargeNumbers(Worksheet sheet) + { + if(sheet == null) + { + throw new ArgumentNullException(nameof(sheet)); + } - var sheetFormatPr = new XElement(Namespaces.workbook + "sheetFormatPr"); - sheetFormatPr.Add(new XAttribute("defaultRowHeight", 15)); - doc.Root.Add(sheetFormatPr); + if (sheet.LargeNumberHandlingMode == LargeNumberHandlingMode.None) + { + return; + } - if (sheet.ColumnWidths.Any()) + if (sheet.LargeNumberHandlingMode == LargeNumberHandlingMode.StoreAsText) + { + foreach (var cellPair in sheet.Cells) { - var cols = new XElement(Namespaces.workbook + "cols"); - foreach (var cw in sheet.ColumnWidths) + if (cellPair.Value.CellType != CellType.Number) { continue; } + var cell = cellPair.Value; + var numVal = (Decimal)cell.Value; + if (!Cell.IsLargeNumber(numVal)) { continue; } + + cell.Format = BuiltInCellFormat.General; + if (cell.HorizontalAlignment == HorizontalAlign.None) { - var rowId = cw.Key + 1; - var col = new XElement(Namespaces.workbook + "col", - new XAttribute("min", rowId), - new XAttribute("max", rowId), - new XAttribute("width", (decimal)cw.Value + ExcelColumnWidthDifference), - new XAttribute("customWidth", 1)); - cols.Add(col); + cell.HorizontalAlignment = HorizontalAlign.Right; } - doc.Root.Add(cols); + cell.IgnoredErrors.NumberStoredAsText = true; } + return; + } - var sheetData = new XElement(Namespaces.workbook + "sheetData"); - XlsxRow firstRow = null; - foreach (var row in rows.OrderBy(rk => rk.Key)) - { - firstRow ??= row.Value; - var re = new XElement(Namespaces.workbook + "row", new XAttribute("r", row.Value.RowIndex)); - foreach (var cell in row.Value.Cells) - { - var ce = new XElement(Namespaces.workbook + "c", - new XAttribute("r", cell.Reference), - new XAttribute("t", cell.CellType), - new XAttribute("s", cell.StyleIndex)); - - if (cell.CellType == XlsxCellTypes.FormulaString) - { - ce.Add(new XElement(Namespaces.workbook + "f", cell.Value)); - } - else - { - ce.Add(new XElement(Namespaces.workbook + "v", cell.Value)); - } + throw new InvalidOperationException($"Unhandled LargeNumberHandlingMode in sheet {sheet.Name}: {sheet.LargeNumberHandlingMode}"); + } - re.Add(ce); - } - sheetData.Add(re); - } - doc.Root.Add(sheetData); + /// + /// Create the xl/worksheets/sheetX.xml file + /// + /// + /// + /// + /// + /// + /// + /// If this worksheet needs an xl/worksheets/_rels/sheetX.xml.rels file + /// + private static Relationship CreateSheetFile(Worksheet sheet, int sheetIndex, RelationshipCounter relationshipCounter, IList styles, XlsxIgnoredErrorCollection ignoredErrors, SharedStrings sharedStrings, out XmlFile sheetRels) + { + var rows = GetXlsxRows(sheet, styles, sharedStrings); - var firstRowCells = firstRow?.Cells; - if (sheet.AutoFilter && firstRowCells?.Count > 0) + var file = new XmlFile + { + ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml", + Path = "xl/worksheets/sheet" + sheetIndex + ".xml" + }; + + var doc = new XDocument(new XElement(Namespaces.workbook + "worksheet", + new XAttribute("xmlns", Namespaces.workbook), + new XAttribute(XNamespace.Xmlns + "r", Namespaces.relationship), + new XAttribute(XNamespace.Xmlns + "mc", Namespaces.mc), + new XAttribute(XNamespace.Xmlns + "x14ac", Namespaces.x14ac), + new XAttribute(XNamespace.Xmlns + "or", Namespaces.officeRelationships), + new XAttribute(Namespaces.mc + "Ignorable", "x14ac") + )); + + WriteSheetViews(sheet, doc); + + var sheetFormatPr = new XElement(Namespaces.workbook + "sheetFormatPr"); + sheetFormatPr.Add(new XAttribute("defaultRowHeight", 15)); + doc.Root.Add(sheetFormatPr); + + if (sheet.ColumnWidths.Any()) + { + var cols = new XElement(Namespaces.workbook + "cols"); + foreach (var cw in sheet.ColumnWidths) { - var firstRef = firstRowCells.First().Reference; - var lastRef = firstRowCells.Last().Reference; - var autoFilter = new XElement(Namespaces.workbook + "autoFilter", new XAttribute("ref", $"{firstRef}:{lastRef}")); - doc.Root.Add(autoFilter); + var rowId = cw.Key + 1; + var col = new XElement(Namespaces.workbook + "col", + new XAttribute("min", rowId), + new XAttribute("max", rowId), + new XAttribute("width", (decimal)cw.Value + ExcelColumnWidthDifference), + new XAttribute("customWidth", 1)); + cols.Add(col); } + doc.Root.Add(cols); + } - sheetRels = null; - var hyperlinks = sheet.Cells.Where(c => c.Value != null && !string.IsNullOrEmpty(c.Value.Hyperlink)).ToList(); - if (hyperlinks.Count > 0) + var sheetData = new XElement(Namespaces.workbook + "sheetData"); + XlsxRow firstRow = null; + foreach (var row in rows.OrderBy(rk => rk.Key)) + { + firstRow ??= row.Value; + var re = new XElement(Namespaces.workbook + "row", new XAttribute("r", row.Value.RowIndex)); + foreach (var cell in row.Value.Cells) { - sheetRels = new XmlFile - { - Path = "xl/worksheets/_rels/sheet" + sheetIndex + ".xml.rels", - ContentType = "application/vnd.openxmlformats-package.relationships+xml" - }; + var ce = new XElement(Namespaces.workbook + "c", + new XAttribute("r", cell.Reference), + new XAttribute("t", cell.CellType), + new XAttribute("s", cell.StyleIndex)); - var hlRelsElem = new XElement(Namespaces.relationship + "Relationships"); - - var hlElem = new XElement(Namespaces.workbook + "hyperlinks"); - for (int i = 0; i <= hyperlinks.Count - 1; i++) + if (cell.CellType == XlsxCellTypes.FormulaString) { - string hyperLinkRelId = "rId" + (i + 1); - - var link = hyperlinks[i]; - var linkElem = new XElement(Namespaces.workbook + "hyperlink", - new XAttribute("ref", link.Key.ToString()), - new XAttribute(Namespaces.officeRelationships + "id", hyperLinkRelId) - ); - hlElem.Add(linkElem); - - hlRelsElem.Add(new XElement(Namespaces.relationship + "Relationship", - new XAttribute("Id", hyperLinkRelId), - new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"), - new XAttribute("Target", link.Value.Hyperlink), - new XAttribute("TargetMode", "External"))); + ce.Add(new XElement(Namespaces.workbook + "f", cell.Value)); + } + else + { + ce.Add(new XElement(Namespaces.workbook + "v", cell.Value)); } - doc.Root.Add(hlElem); - sheetRels.Content = new XDocument(); - sheetRels.Content.Add(hlRelsElem); - } - var pageSetup = new XElement(Namespaces.workbook + "pageSetup"); - pageSetup.Add(new XAttribute("orientation", sheet.PageSetup.Orientation == Orientation.Portrait ? "portrait" : "landscape")); - doc.Root.Add(pageSetup); + re.Add(ce); + } + sheetData.Add(re); + } + doc.Root.Add(sheetData); - WritePageBreaks(sheet, doc); - WriteIgnoredErrors(ignoredErrors, doc); + var firstRowCells = firstRow?.Cells; + if (sheet.AutoFilter && firstRowCells?.Count > 0) + { + var firstRef = firstRowCells.First().Reference; + var lastRef = firstRowCells.Last().Reference; + var autoFilter = new XElement(Namespaces.workbook + "autoFilter", new XAttribute("ref", $"{firstRef}:{lastRef}")); + doc.Root.Add(autoFilter); + } - file.Content = doc; - var rel = new Relationship(relationshipCounter) + sheetRels = null; + var hyperlinks = sheet.Cells.Where(c => c.Value != null && !string.IsNullOrEmpty(c.Value.Hyperlink)).ToList(); + if (hyperlinks.Count > 0) + { + sheetRels = new XmlFile { - Target = file, - Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" + Path = "xl/worksheets/_rels/sheet" + sheetIndex + ".xml.rels", + ContentType = "application/vnd.openxmlformats-package.relationships+xml" }; - return rel; + var hlRelsElem = new XElement(Namespaces.relationship + "Relationships"); + + var hlElem = new XElement(Namespaces.workbook + "hyperlinks"); + for (int i = 0; i <= hyperlinks.Count - 1; i++) + { + string hyperLinkRelId = "rId" + (i + 1); + + var link = hyperlinks[i]; + var linkElem = new XElement(Namespaces.workbook + "hyperlink", + new XAttribute("ref", link.Key.ToString()), + new XAttribute(Namespaces.officeRelationships + "id", hyperLinkRelId) + ); + hlElem.Add(linkElem); + + hlRelsElem.Add(new XElement(Namespaces.relationship + "Relationship", + new XAttribute("Id", hyperLinkRelId), + new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"), + new XAttribute("Target", link.Value.Hyperlink), + new XAttribute("TargetMode", "External"))); + } + doc.Root.Add(hlElem); + sheetRels.Content = new XDocument(); + sheetRels.Content.Add(hlRelsElem); } - private static void WriteIgnoredErrors(XlsxIgnoredErrorCollection ignoredErrors, XDocument doc) + var pageSetup = new XElement(Namespaces.workbook + "pageSetup"); + pageSetup.Add(new XAttribute("orientation", sheet.PageSetup.Orientation == Orientation.Portrait ? "portrait" : "landscape")); + doc.Root.Add(pageSetup); + + WritePageBreaks(sheet, doc); + WriteIgnoredErrors(ignoredErrors, doc); + + file.Content = doc; + var rel = new Relationship(relationshipCounter) { - if (ignoredErrors == null) { throw new ArgumentNullException(nameof(ignoredErrors)); } - if (doc == null) { throw new ArgumentNullException(nameof(doc)); } + Target = file, + Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" + }; - if (ignoredErrors.DistinctIgnoredErrors.Count == 0) - { - return; - } + return rel; + } - var igElem = new XElement(Namespaces.workbook + "ignoredErrors"); + private static void WriteIgnoredErrors(XlsxIgnoredErrorCollection ignoredErrors, XDocument doc) + { + if (ignoredErrors == null) { throw new ArgumentNullException(nameof(ignoredErrors)); } + if (doc == null) { throw new ArgumentNullException(nameof(doc)); } - foreach (var ie in ignoredErrors.DistinctIgnoredErrors) - { - var elem = new XElement(Namespaces.workbook + "ignoredError"); - elem.Add(new XAttribute("sqref", ie.GetSqRef())); - - if (ie.IgnoredError.EvalError) { elem.Add(new XAttribute("evalError", 1)); } - if (ie.IgnoredError.TwoDigitTextYear) { elem.Add(new XAttribute("twoDigitTextYear", 1)); } - if (ie.IgnoredError.NumberStoredAsText) { elem.Add(new XAttribute("numberStoredAsText", 1)); } - if (ie.IgnoredError.Formula) { elem.Add(new XAttribute("formula", 1)); } - if (ie.IgnoredError.FormulaRange) { elem.Add(new XAttribute("formulaRange", 1)); } - if (ie.IgnoredError.UnlockedFormula) { elem.Add(new XAttribute("unlockedFormula", 1)); } - if (ie.IgnoredError.EmptyCellReference) { elem.Add(new XAttribute("emptyCellReference", 1)); } - if (ie.IgnoredError.ListDataValidation) { elem.Add(new XAttribute("listDataValidation", 1)); } - if (ie.IgnoredError.CalculatedColumn) { elem.Add(new XAttribute("calculatedColumn", 1)); } - igElem.Add(elem); - } + if (ignoredErrors.DistinctIgnoredErrors.Count == 0) + { + return; + } + + var igElem = new XElement(Namespaces.workbook + "ignoredErrors"); - doc.Root.Add(igElem); + foreach (var ie in ignoredErrors.DistinctIgnoredErrors) + { + var elem = new XElement(Namespaces.workbook + "ignoredError"); + elem.Add(new XAttribute("sqref", ie.GetSqRef())); + + if (ie.IgnoredError.EvalError) { elem.Add(new XAttribute("evalError", 1)); } + if (ie.IgnoredError.TwoDigitTextYear) { elem.Add(new XAttribute("twoDigitTextYear", 1)); } + if (ie.IgnoredError.NumberStoredAsText) { elem.Add(new XAttribute("numberStoredAsText", 1)); } + if (ie.IgnoredError.Formula) { elem.Add(new XAttribute("formula", 1)); } + if (ie.IgnoredError.FormulaRange) { elem.Add(new XAttribute("formulaRange", 1)); } + if (ie.IgnoredError.UnlockedFormula) { elem.Add(new XAttribute("unlockedFormula", 1)); } + if (ie.IgnoredError.EmptyCellReference) { elem.Add(new XAttribute("emptyCellReference", 1)); } + if (ie.IgnoredError.ListDataValidation) { elem.Add(new XAttribute("listDataValidation", 1)); } + if (ie.IgnoredError.CalculatedColumn) { elem.Add(new XAttribute("calculatedColumn", 1)); } + igElem.Add(elem); } - private static Dictionary GetXlsxRows(Worksheet sheet, IList styles, SharedStrings sharedStrings) + doc.Root.Add(igElem); + } + + private static Dictionary GetXlsxRows(Worksheet sheet, IList styles, SharedStrings sharedStrings) + { + var rows = new Dictionary(); + if(!Enum.IsDefined(typeof(LargeNumberHandlingMode), sheet.LargeNumberHandlingMode)) { - var rows = new Dictionary(); - if(!Enum.IsDefined(typeof(LargeNumberHandlingMode), sheet.LargeNumberHandlingMode)) - { - throw new InvalidOperationException($"Invalid value for {nameof(Worksheet.LargeNumberHandlingMode)} in sheet {sheet.Name}: {sheet.LargeNumberHandlingMode}"); - } + throw new InvalidOperationException($"Invalid value for {nameof(Worksheet.LargeNumberHandlingMode)} in sheet {sheet.Name}: {sheet.LargeNumberHandlingMode}"); + } - // The order matters! - foreach (var cell in sheet.Cells.OrderBy(c => c.Key.Row).ThenBy(c => c.Key.Column)) + // The order matters! + foreach (var cell in sheet.Cells.OrderBy(c => c.Key.Row).ThenBy(c => c.Key.Column)) + { + if (!rows.ContainsKey(cell.Key.Row)) { - if (!rows.ContainsKey(cell.Key.Row)) - { - rows[cell.Key.Row] = new XlsxRow { RowIndex = cell.Key.Row + 1 }; - } + rows[cell.Key.Row] = new XlsxRow { RowIndex = cell.Key.Row + 1 }; + } - var styleIndex = styles.IndexOf(cell.Value.XlsxCellStyle) + 1; + var styleIndex = styles.IndexOf(cell.Value.XlsxCellStyle) + 1; - var xc = new XlsxCell - { - StyleIndex = styleIndex, - Reference = cell.Key.ToString() - }; + var xc = new XlsxCell + { + StyleIndex = styleIndex, + Reference = cell.Key.ToString() + }; - switch (cell.Value.CellType) - { - case CellType.Text: - xc.CellType = XlsxCellTypes.SharedString; - xc.Value = sharedStrings.GetStringIndex((string)cell.Value.Value); - break; - case CellType.Formula: - xc.CellType = XlsxCellTypes.FormulaString; - xc.Value = (string)cell.Value.Value; - break; - case CellType.Number: - // Fun: Excel can't handle large numbers as numbers - // https://support.microsoft.com/en-us/help/2643223/long-numbers-are-displayed-incorrectly-in-excel - var numVal = (Decimal)cell.Value.Value; - if (sheet.LargeNumberHandlingMode != LargeNumberHandlingMode.None && Cell.IsLargeNumber(numVal)) - { - switch (sheet.LargeNumberHandlingMode) - { - case LargeNumberHandlingMode.StoreAsText: - xc.CellType = XlsxCellTypes.SharedString; - xc.Value = sharedStrings.GetStringIndex(numVal.ToString(System.Globalization.CultureInfo.InvariantCulture)); - break; - default: - throw new InvalidOperationException("Unhandled LargeNumberHandlingMode: " + sheet.LargeNumberHandlingMode); - } - } - else + switch (cell.Value.CellType) + { + case CellType.Text: + xc.CellType = XlsxCellTypes.SharedString; + xc.Value = sharedStrings.GetStringIndex((string)cell.Value.Value); + break; + case CellType.Formula: + xc.CellType = XlsxCellTypes.FormulaString; + xc.Value = (string)cell.Value.Value; + break; + case CellType.Number: + // Fun: Excel can't handle large numbers as numbers + // https://support.microsoft.com/en-us/help/2643223/long-numbers-are-displayed-incorrectly-in-excel + var numVal = (Decimal)cell.Value.Value; + if (sheet.LargeNumberHandlingMode != LargeNumberHandlingMode.None && Cell.IsLargeNumber(numVal)) + { + switch (sheet.LargeNumberHandlingMode) { - xc.CellType = XlsxCellTypes.Number; - xc.Value = ((Decimal)cell.Value.Value).ToString(System.Globalization.CultureInfo.InvariantCulture); + case LargeNumberHandlingMode.StoreAsText: + xc.CellType = XlsxCellTypes.SharedString; + xc.Value = sharedStrings.GetStringIndex(numVal.ToString(System.Globalization.CultureInfo.InvariantCulture)); + break; + default: + throw new InvalidOperationException("Unhandled LargeNumberHandlingMode: " + sheet.LargeNumberHandlingMode); } - break; - case CellType.Date: + } + else + { xc.CellType = XlsxCellTypes.Number; - if (cell.Value.Value != null) - { - xc.Value = ((DateTime)cell.Value.Value).ToOADate(); - } - break; - default: - throw new ArgumentException("Unknown Cell Type: " + cell.Value.CellType + " in cell " + cell.Key.ToString() + " of " + sheet.Name); - } - - rows[cell.Key.Row].Cells.Add(xc); + xc.Value = ((Decimal)cell.Value.Value).ToString(System.Globalization.CultureInfo.InvariantCulture); + } + break; + case CellType.Date: + xc.CellType = XlsxCellTypes.Number; + if (cell.Value.Value != null) + { + xc.Value = ((DateTime)cell.Value.Value).ToOADate(); + } + break; + default: + throw new ArgumentException("Unknown Cell Type: " + cell.Value.CellType + " in cell " + cell.Key.ToString() + " of " + sheet.Name); } - return rows; + + rows[cell.Key.Row].Cells.Add(xc); } + return rows; } } } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XmlFile.cs b/src/Simplexcel/XlsxInternal/XmlFile.cs index a0e88f1..980210d 100644 --- a/src/Simplexcel/XlsxInternal/XmlFile.cs +++ b/src/Simplexcel/XlsxInternal/XmlFile.cs @@ -1,30 +1,29 @@ using System.Xml.Linq; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +/// +/// An XML File in the package +/// +internal class XmlFile { /// - /// An XML File in the package + /// The path to the file within the package, without leading / /// - internal class XmlFile - { - /// - /// The path to the file within the package, without leading / - /// - internal string Path { get; set; } + internal string Path { get; set; } - /// - /// The Content Type of the file (default: application/xml) - /// - internal string ContentType { get; set; } + /// + /// The Content Type of the file (default: application/xml) + /// + internal string ContentType { get; set; } - /// - /// The actual file content - /// - internal XDocument Content { get; set; } + /// + /// The actual file content + /// + internal XDocument Content { get; set; } - public XmlFile() - { - ContentType = "application/xml"; - } + public XmlFile() + { + ContentType = "application/xml"; } -} +} \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/ZipPackage.cs b/src/Simplexcel/XlsxInternal/ZipPackage.cs index 418d9a7..d337db2 100644 --- a/src/Simplexcel/XlsxInternal/ZipPackage.cs +++ b/src/Simplexcel/XlsxInternal/ZipPackage.cs @@ -5,139 +5,138 @@ using System.Text; using System.Xml.Linq; -namespace Simplexcel.XlsxInternal +namespace Simplexcel.XlsxInternal; + +internal sealed class ZipPackage : IDisposable { - internal sealed class ZipPackage : IDisposable - { - private readonly ZipArchive _archive; - private readonly IDictionary _contentTypes; - private readonly IList _relationships; - private bool _closed; - private readonly bool _compress; + private readonly ZipArchive _archive; + private readonly IDictionary _contentTypes; + private readonly IList _relationships; + private bool _closed; + private readonly bool _compress; - internal ZipPackage(Stream underlyingStream, bool useCompression) + internal ZipPackage(Stream underlyingStream, bool useCompression) + { + if (underlyingStream == null) { - if (underlyingStream == null) - { - throw new ArgumentNullException(nameof(underlyingStream)); - } - - _archive = new ZipArchive(underlyingStream, ZipArchiveMode.Create, true); - _contentTypes = new Dictionary(); - _relationships = new List(); - _compress = useCompression; + throw new ArgumentNullException(nameof(underlyingStream)); } - internal void AddRelationship(Relationship rel) - { - CheckClosed(); - _relationships.Add(rel); - } + _archive = new ZipArchive(underlyingStream, ZipArchiveMode.Create, true); + _contentTypes = new Dictionary(); + _relationships = new List(); + _compress = useCompression; + } - internal void Close() - { - CheckClosed(); - WriteContentTypes(); - WriteRelationships(); - _archive.Dispose(); - _closed = true; - } + internal void AddRelationship(Relationship rel) + { + CheckClosed(); + _relationships.Add(rel); + } - public void Dispose() - { - if (!_closed) - { - Close(); - } - } + internal void Close() + { + CheckClosed(); + WriteContentTypes(); + WriteRelationships(); + _archive.Dispose(); + _closed = true; + } - private void CheckClosed() + public void Dispose() + { + if (!_closed) { - if (_closed) - { - throw new InvalidOperationException("This " + nameof(ZipPackage) + " is already closed."); - } + Close(); } + } - internal void WriteXmlFile(XmlFile file) + private void CheckClosed() + { + if (_closed) { - CheckClosed(); - _contentTypes["/" + file.Path] = file.ContentType; - AddFile(file.Path, file.Content); + throw new InvalidOperationException("This " + nameof(ZipPackage) + " is already closed."); } + } - private void AddFile(string relativeFileName, XDocument xdoc) - { - CheckClosed(); + internal void WriteXmlFile(XmlFile file) + { + CheckClosed(); + _contentTypes["/" + file.Path] = file.ContentType; + AddFile(file.Path, file.Content); + } - if (xdoc == null) - { - throw new ArgumentNullException(nameof(xdoc)); - } + private void AddFile(string relativeFileName, XDocument xdoc) + { + CheckClosed(); - byte[] content = Encoding.UTF8.GetBytes(xdoc.ToString()); - AddFile(relativeFileName, content); + if (xdoc == null) + { + throw new ArgumentNullException(nameof(xdoc)); } - private void AddFile(string relativeFileName, byte[] content) + byte[] content = Encoding.UTF8.GetBytes(xdoc.ToString()); + AddFile(relativeFileName, content); + } + + private void AddFile(string relativeFileName, byte[] content) + { + CheckClosed(); + if (string.IsNullOrEmpty(relativeFileName)) { - CheckClosed(); - if (string.IsNullOrEmpty(relativeFileName)) - { - throw new ArgumentException("Invalid " + nameof(relativeFileName)); - } - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } + throw new ArgumentException("Invalid " + nameof(relativeFileName)); + } + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } - var entry = _archive.CreateEntry(relativeFileName, _compress ? CompressionLevel.Optimal : CompressionLevel.NoCompression); + var entry = _archive.CreateEntry(relativeFileName, _compress ? CompressionLevel.Optimal : CompressionLevel.NoCompression); - using (var stream = entry.Open()) - { - stream.Write(content, 0, content.Length); - } + using (var stream = entry.Open()) + { + stream.Write(content, 0, content.Length); } + } - private void WriteContentTypes() - { - CheckClosed(); - const string defaultXmlContentType = "application/xml"; - var root = new XElement(Namespaces.contenttypes + "Types"); - root.Add(new XElement(Namespaces.contenttypes + "Default", new XAttribute("Extension", "xml"), new XAttribute("ContentType", defaultXmlContentType))); - root.Add(new XElement(Namespaces.contenttypes + "Default", new XAttribute("Extension", "rels"), new XAttribute("ContentType", "application/vnd.openxmlformats-package.relationships+xml"))); + private void WriteContentTypes() + { + CheckClosed(); + const string defaultXmlContentType = "application/xml"; + var root = new XElement(Namespaces.contenttypes + "Types"); + root.Add(new XElement(Namespaces.contenttypes + "Default", new XAttribute("Extension", "xml"), new XAttribute("ContentType", defaultXmlContentType))); + root.Add(new XElement(Namespaces.contenttypes + "Default", new XAttribute("Extension", "rels"), new XAttribute("ContentType", "application/vnd.openxmlformats-package.relationships+xml"))); - foreach (var ct in _contentTypes) + foreach (var ct in _contentTypes) + { + if (string.Equals(Path.GetExtension(ct.Key), ".xml", StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(Path.GetExtension(ct.Key), ".xml", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(ct.Value, defaultXmlContentType)) { - if (!string.Equals(ct.Value, defaultXmlContentType)) - { - var ovr = new XElement(Namespaces.contenttypes + "Override"); - ovr.Add(new XAttribute("PartName", ct.Key)); - ovr.Add(new XAttribute("ContentType", ct.Value)); - root.Add(ovr); - } + var ovr = new XElement(Namespaces.contenttypes + "Override"); + ovr.Add(new XAttribute("PartName", ct.Key)); + ovr.Add(new XAttribute("ContentType", ct.Value)); + root.Add(ovr); } } - - AddFile("[Content_Types].xml", new XDocument(root)); } - private void WriteRelationships() - { - CheckClosed(); - var root = new XElement(Namespaces.relationship + "Relationships"); - foreach(var rel in _relationships) - { - var re = new XElement(Namespaces.relationship + "Relationship"); - re.Add(new XAttribute("Type", rel.Type)); - re.Add(new XAttribute("Target", "/" + rel.Target.Path)); - re.Add(new XAttribute("Id", rel.Id)); - root.Add(re); - } + AddFile("[Content_Types].xml", new XDocument(root)); + } - AddFile("_rels/.rels", new XDocument(root)); + private void WriteRelationships() + { + CheckClosed(); + var root = new XElement(Namespaces.relationship + "Relationships"); + foreach(var rel in _relationships) + { + var re = new XElement(Namespaces.relationship + "Relationship"); + re.Add(new XAttribute("Type", rel.Type)); + re.Add(new XAttribute("Target", "/" + rel.Target.Path)); + re.Add(new XAttribute("Id", rel.Id)); + root.Add(re); } + + AddFile("_rels/.rels", new XDocument(root)); } } \ No newline at end of file From 802846d5a7655334a744b8182e3f844acfd8c942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 18 Dec 2024 08:46:45 +0100 Subject: [PATCH 05/12] Leverage modern C# language features --- src/Simplexcel.TestApp/Program.cs | 3 +- src/Simplexcel/CellAddressHelper.cs | 11 ++-- src/Simplexcel/Cells/Cell.cs | 50 ++++++++--------- src/Simplexcel/Cells/CellCollection.cs | 12 ++--- src/Simplexcel/Cells/GradientFill.cs | 7 +-- src/Simplexcel/Cells/PatternFill.cs | 8 +-- src/Simplexcel/Cells/XlsxColumnAttribute.cs | 10 ++-- src/Simplexcel/ColumnWidthCollection.cs | 4 +- src/Simplexcel/ExtensionMethods.Internal.cs | 2 +- src/Simplexcel/SimplexcelVersion.cs | 1 - src/Simplexcel/Workbook.cs | 9 ++-- src/Simplexcel/Worksheet.Populate.cs | 22 +++----- src/Simplexcel/Worksheet.cs | 54 ++++++------------- src/Simplexcel/WorksheetView/SheetView.cs | 22 +++----- src/Simplexcel/XlsxInternal/Relationship.cs | 9 +--- src/Simplexcel/XlsxInternal/SharedStrings.cs | 12 ++--- src/Simplexcel/XlsxInternal/StyleWriter.cs | 48 ++++++++--------- src/Simplexcel/XlsxInternal/XlsxCellStyle.cs | 10 +--- .../XlsxInternal/XlsxIgnoredError.cs | 17 ++---- .../XlsxIgnoredErrorCollection.cs | 7 +-- src/Simplexcel/XlsxInternal/XlsxPackage.cs | 18 ++----- src/Simplexcel/XlsxInternal/XlsxRow.cs | 7 +-- src/Simplexcel/XlsxInternal/XlsxWriter.cs | 10 ++-- src/Simplexcel/XlsxInternal/XmlFile.cs | 7 +-- src/Simplexcel/XlsxInternal/ZipPackage.cs | 6 +-- 25 files changed, 134 insertions(+), 232 deletions(-) diff --git a/src/Simplexcel.TestApp/Program.cs b/src/Simplexcel.TestApp/Program.cs index 1a18815..89396e4 100644 --- a/src/Simplexcel.TestApp/Program.cs +++ b/src/Simplexcel.TestApp/Program.cs @@ -243,7 +243,8 @@ private class PopulateTestData : PopulateTestDataBase public long Value { get; set; } [XlsxColumn("Total")] - public decimal TotalPrice { get { return Price * Quantity; } } + public decimal TotalPrice => Price * Quantity; + public DateTime CreatedUtc { get; set; } [XlsxIgnoreColumn] diff --git a/src/Simplexcel/CellAddressHelper.cs b/src/Simplexcel/CellAddressHelper.cs index 6a979dc..b8899ce 100644 --- a/src/Simplexcel/CellAddressHelper.cs +++ b/src/Simplexcel/CellAddressHelper.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; namespace Simplexcel; internal static class CellAddressHelper { - private readonly static Regex _cellAddressRegex = new Regex(@"(?[a-z]+)(?[0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private readonly static char[] _chars = new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; + private static readonly Regex CellAddressRegex = new("(?[a-z]+)(?[0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly char[] Chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; /// /// Convert a zero-based row and column to an Excel Cell Reference, e.g. A1 for [0,0] @@ -46,7 +45,7 @@ internal static void ReferenceToColRow(string reference, out int row, out int co { row = column = 0; - var parts = _cellAddressRegex.Match(reference); + var parts = CellAddressRegex.Match(reference); if (parts.Success) { var rowMatch = parts.Groups["Row"]; @@ -60,7 +59,7 @@ internal static void ReferenceToColRow(string reference, out int row, out int co for (int i = 0; i < colString.Length; i++) { char c = colString[colString.Length - i - 1]; - var ix = Array.IndexOf(_chars, c); + var ix = Array.IndexOf(Chars, c); if (ix == -1) { throw new ArgumentException("Cell Reference needs to be in Excel format, e.g. A1 or BA321. Invalid: " + reference, nameof(reference)); @@ -84,7 +83,7 @@ internal static IList DetermineRanges(ICollection cellAddre var result = new List(); // TODO: Actually support Ranges. Ranges are Rectangular, e.g., A1:B5 (TopLeft:BottomRight) - foreach (var ca in cellAddresses ?? Enumerable.Empty()) + foreach (var ca in cellAddresses ?? []) { result.Add(ca.ToString()); } diff --git a/src/Simplexcel/Cells/Cell.cs b/src/Simplexcel/Cells/Cell.cs index f8209fe..5d4b984 100644 --- a/src/Simplexcel/Cells/Cell.cs +++ b/src/Simplexcel/Cells/Cell.cs @@ -41,8 +41,8 @@ public Cell(CellType type, object value, string format) /// public string Format { - get { return XlsxCellStyle.Format; } - set { XlsxCellStyle.Format = value; } + get => XlsxCellStyle.Format; + set => XlsxCellStyle.Format = value; } /// @@ -50,8 +50,8 @@ public string Format /// public CellBorder Border { - get { return XlsxCellStyle.Border; } - set { XlsxCellStyle.Border = value; } + get => XlsxCellStyle.Border; + set => XlsxCellStyle.Border = value; } /// @@ -59,8 +59,8 @@ public CellBorder Border /// public string FontName { - get { return XlsxCellStyle.Font.Name; } - set { XlsxCellStyle.Font.Name = value; } + get => XlsxCellStyle.Font.Name; + set => XlsxCellStyle.Font.Name = value; } /// @@ -68,8 +68,8 @@ public string FontName /// public int FontSize { - get { return XlsxCellStyle.Font.Size; } - set { XlsxCellStyle.Font.Size = value; } + get => XlsxCellStyle.Font.Size; + set => XlsxCellStyle.Font.Size = value; } /// @@ -77,8 +77,8 @@ public int FontSize /// public bool Bold { - get { return XlsxCellStyle.Font.Bold; } - set { XlsxCellStyle.Font.Bold = value; } + get => XlsxCellStyle.Font.Bold; + set => XlsxCellStyle.Font.Bold = value; } /// @@ -86,8 +86,8 @@ public bool Bold /// public bool Italic { - get { return XlsxCellStyle.Font.Italic; } - set { XlsxCellStyle.Font.Italic = value; } + get => XlsxCellStyle.Font.Italic; + set => XlsxCellStyle.Font.Italic = value; } /// @@ -95,8 +95,8 @@ public bool Italic /// public bool Underline { - get { return XlsxCellStyle.Font.Underline; } - set { XlsxCellStyle.Font.Underline = value; } + get => XlsxCellStyle.Font.Underline; + set => XlsxCellStyle.Font.Underline = value; } /// @@ -104,8 +104,8 @@ public bool Underline /// public Color TextColor { - get { return XlsxCellStyle.Font.TextColor; } - set { XlsxCellStyle.Font.TextColor = value; } + get => XlsxCellStyle.Font.TextColor; + set => XlsxCellStyle.Font.TextColor = value; } /// @@ -113,8 +113,8 @@ public Color TextColor /// public PatternFill Fill { - get { return XlsxCellStyle.Fill; } - set { XlsxCellStyle.Fill = value; } + get => XlsxCellStyle.Fill; + set => XlsxCellStyle.Fill = value; } /// @@ -122,8 +122,8 @@ public PatternFill Fill /// public HorizontalAlign HorizontalAlignment { - get { return XlsxCellStyle.HorizontalAlignment; } - set { XlsxCellStyle.HorizontalAlignment = value; } + get => XlsxCellStyle.HorizontalAlignment; + set => XlsxCellStyle.HorizontalAlignment = value; } /// @@ -131,8 +131,8 @@ public HorizontalAlign HorizontalAlignment /// public VerticalAlign VerticalAlignment { - get { return XlsxCellStyle.VerticalAlignment; } - set { XlsxCellStyle.VerticalAlignment = value; } + get => XlsxCellStyle.VerticalAlignment; + set => XlsxCellStyle.VerticalAlignment = value; } /// @@ -223,11 +223,11 @@ public static implicit operator Cell(DateTime value) public static Cell FromObject(object val) { Cell cell; - if (val is sbyte || val is short || val is int || val is long || val is byte || val is uint || val is ushort || val is ulong) + if (val is sbyte or short or int or long or byte or uint or ushort or ulong) { cell = new Cell(CellType.Number, Convert.ToDecimal(val), BuiltInCellFormat.NumberNoDecimalPlaces); } - else if (val is float || val is double || val is decimal) + else if (val is float or double or decimal) { cell = new Cell(CellType.Number, Convert.ToDecimal(val), BuiltInCellFormat.General); } @@ -237,7 +237,7 @@ public static Cell FromObject(object val) } else { - cell = new Cell(CellType.Text, (val ?? String.Empty).ToString(), BuiltInCellFormat.Text); + cell = new Cell(CellType.Text, val?.ToString() ?? string.Empty, BuiltInCellFormat.Text); } return cell; } diff --git a/src/Simplexcel/Cells/CellCollection.cs b/src/Simplexcel/Cells/CellCollection.cs index 6a5d0af..4c7a640 100644 --- a/src/Simplexcel/Cells/CellCollection.cs +++ b/src/Simplexcel/Cells/CellCollection.cs @@ -8,7 +8,7 @@ namespace Simplexcel; /// public sealed class CellCollection : IEnumerable> { - private readonly Dictionary _cells = new Dictionary(); + private readonly Dictionary _cells = new(); /// /// How many Rows are in the sheet? (This counts the maximum row index, so empty rows are counted if they are followed by another row) @@ -33,8 +33,8 @@ public int ColumnCount /// The Cell, or NULL of the Cell hasn't been created yet. public Cell this[string address] { - get { return this[new CellAddress(address)]; } - set { this[new CellAddress(address)] = value; } + get => this[new CellAddress(address)]; + set => this[new CellAddress(address)] = value; } /// @@ -45,8 +45,8 @@ public Cell this[string address] /// The Cell, or NULL of the Cell hasn't been created yet. public Cell this[int row, int column] { - get { return this[new CellAddress(row, column)]; } - set { this[new CellAddress(row, column)] = value; } + get => this[new CellAddress(row, column)]; + set => this[new CellAddress(row, column)] = value; } private Cell this[CellAddress key] @@ -58,7 +58,7 @@ private Cell this[CellAddress key] return _cells[key]; } - set { _cells[key] = value; } + set => _cells[key] = value; } /// diff --git a/src/Simplexcel/Cells/GradientFill.cs b/src/Simplexcel/Cells/GradientFill.cs index 9bac03f..71d6da4 100644 --- a/src/Simplexcel/Cells/GradientFill.cs +++ b/src/Simplexcel/Cells/GradientFill.cs @@ -7,12 +7,7 @@ namespace Simplexcel; // TODO: Not Yet Implemented internal class GradientFill : IEquatable { - public IList Stops { get; } - - public GradientFill() - { - Stops = new List(); - } + public IList Stops { get; } = []; public override bool Equals(object obj) { diff --git a/src/Simplexcel/Cells/PatternFill.cs b/src/Simplexcel/Cells/PatternFill.cs index 14506f0..6e0beea 100644 --- a/src/Simplexcel/Cells/PatternFill.cs +++ b/src/Simplexcel/Cells/PatternFill.cs @@ -10,7 +10,7 @@ public class PatternFill : IEquatable // TODO: Add a Gradient Fill to this. // This isn't the structure in the XML, but that's how Excel Presents it, as a "Fill Effect" - bool firstTimeSet = false; + private bool _firstTimeSet; private Color? _bgColor; /// @@ -37,11 +37,11 @@ public Color? BackgroundColor // PatternType defaults to None, but the first time we set a background color, // set it to solid as the user likely wants the background color to show. - // Further modifications won't change the PatternType, as this is now a delibrate setting - if (_bgColor.HasValue && PatternType == PatternType.None && !firstTimeSet) + // Further modifications won't change the PatternType, as this is now a deliberate setting + if (_bgColor.HasValue && PatternType == PatternType.None && !_firstTimeSet) { PatternType = PatternType.Solid; - firstTimeSet = true; + _firstTimeSet = true; } } } diff --git a/src/Simplexcel/Cells/XlsxColumnAttribute.cs b/src/Simplexcel/Cells/XlsxColumnAttribute.cs index 8dd584a..ed2efd9 100644 --- a/src/Simplexcel/Cells/XlsxColumnAttribute.cs +++ b/src/Simplexcel/Cells/XlsxColumnAttribute.cs @@ -3,7 +3,7 @@ namespace Simplexcel; /// -/// Used with or , allows setting how object properties are handled. +/// Used with or , allows setting how object properties are handled. /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class XlsxColumnAttribute : Attribute @@ -19,7 +19,7 @@ public class XlsxColumnAttribute : Attribute /// The Index of the Column, e.g., "0" for "A". /// If there are Columns with and without an Index, the columns /// without an Index will be added after the last column with an Index. - /// + /// /// It is recommended that either all object properties or none specify Column Indexes /// public int ColumnIndex { get; set; } @@ -44,9 +44,7 @@ public XlsxColumnAttribute(string name) } /// -/// This attribute causes to ignore the property completely +/// This attribute causes to ignore the property completely /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] -public sealed class XlsxIgnoreColumnAttribute : Attribute -{ -} \ No newline at end of file +public sealed class XlsxIgnoreColumnAttribute : Attribute; \ No newline at end of file diff --git a/src/Simplexcel/ColumnWidthCollection.cs b/src/Simplexcel/ColumnWidthCollection.cs index 1ff27a2..7efa08b 100644 --- a/src/Simplexcel/ColumnWidthCollection.cs +++ b/src/Simplexcel/ColumnWidthCollection.cs @@ -8,7 +8,7 @@ namespace Simplexcel; /// public sealed class ColumnWidthCollection : IEnumerable> { - private readonly Dictionary _columnWidths = new Dictionary(); + private readonly Dictionary _columnWidths = new(); /// /// Get or set the width of a column (Zero-based column index, null value = auto) @@ -17,7 +17,7 @@ public sealed class ColumnWidthCollection : IEnumerable public double? this[int column] { - get { return _columnWidths.ContainsKey(column) ? _columnWidths[column] : (double?)null; } + get => _columnWidths.ContainsKey(column) ? _columnWidths[column] : null; set { if (!value.HasValue) diff --git a/src/Simplexcel/ExtensionMethods.Internal.cs b/src/Simplexcel/ExtensionMethods.Internal.cs index 2aac121..f85d76d 100644 --- a/src/Simplexcel/ExtensionMethods.Internal.cs +++ b/src/Simplexcel/ExtensionMethods.Internal.cs @@ -47,7 +47,7 @@ internal static int GetCollectionHashCode(this IEnumerable input) return hashCode; } - private static readonly char[] HexEncodingTable = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + private static readonly char[] HexEncodingTable = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; /// /// Taken from Bouncy Castle and slightly changed to create a string rather than write to a stream. diff --git a/src/Simplexcel/SimplexcelVersion.cs b/src/Simplexcel/SimplexcelVersion.cs index b9e4eaf..81a4636 100644 --- a/src/Simplexcel/SimplexcelVersion.cs +++ b/src/Simplexcel/SimplexcelVersion.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Reflection; namespace Simplexcel; diff --git a/src/Simplexcel/Workbook.cs b/src/Simplexcel/Workbook.cs index c8c711d..70cc4f9 100644 --- a/src/Simplexcel/Workbook.cs +++ b/src/Simplexcel/Workbook.cs @@ -11,12 +11,12 @@ namespace Simplexcel; /// public sealed class Workbook { - private readonly List _sheets = new List(); + private readonly List _sheets = []; /// /// The Worksheets in this Workbook /// - public IEnumerable Sheets { get { return _sheets.AsEnumerable(); } } + public IEnumerable Sheets => _sheets.AsEnumerable(); /// /// The title of the Workbook @@ -31,10 +31,7 @@ public sealed class Workbook /// /// How many sheets are in the Workbook currently? /// - public int SheetCount - { - get { return _sheets.Count; } - } + public int SheetCount => _sheets.Count; /// /// Add a worksheet to this workbook. Sheet names must be unique. diff --git a/src/Simplexcel/Worksheet.Populate.cs b/src/Simplexcel/Worksheet.Populate.cs index 62ad142..bef1c4e 100644 --- a/src/Simplexcel/Worksheet.Populate.cs +++ b/src/Simplexcel/Worksheet.Populate.cs @@ -3,19 +3,18 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading; namespace Simplexcel; public sealed partial class Worksheet { - private readonly static Lazy>> PopulateCache - = new Lazy>>( - () => new ConcurrentDictionary>(), - System.Threading.LazyThreadSafetyMode.ExecutionAndPublication); + private static readonly Lazy>> PopulateCache + = new(() => new ConcurrentDictionary>(), LazyThreadSafetyMode.ExecutionAndPublication); /// /// Create Worksheet with the provided data. - /// + /// /// Will use the Object Property Names as Column Headers (First Row) and then populate the cells with data. /// You can use and to control the output. /// @@ -31,7 +30,7 @@ public static Worksheet FromData(string sheetName, IEnumerable data, bool /// /// Populate Worksheet with the provided data. - /// + /// /// Will use the Object Property Names as Column Headers (First Row) and then populate the cells with data. /// You can use and to control the output. /// @@ -39,7 +38,7 @@ public static Worksheet FromData(string sheetName, IEnumerable data, bool /// If true, the Column info for the given type is being cached in memory public void Populate(IEnumerable data, bool cacheTypeColumns = false) where T : class { - data ??= Enumerable.Empty(); + data ??= []; var type = typeof(T); @@ -120,7 +119,7 @@ private static Dictionary GetColumsFromType(Type type) private static void AddToPopulateCache(Type type, Dictionary cols) { - PopulateCache.Value.AddOrUpdate(type, cols, (u1, u2) => cols); + PopulateCache.Value.AddOrUpdate(type, cols, (_, _) => cols); } private static Dictionary TryGetFromCache(Type type) @@ -130,12 +129,7 @@ private static Dictionary TryGetFromCache(Type type) return null; } - if (PopulateCache.Value.TryGetValue(type, out var cached)) - { - return cached; - } - - return null; + return PopulateCache.Value.TryGetValue(type, out var cached) ? cached : null; } private class PopulateCellInfo diff --git a/src/Simplexcel/Worksheet.cs b/src/Simplexcel/Worksheet.cs index 45c4680..63263d5 100644 --- a/src/Simplexcel/Worksheet.cs +++ b/src/Simplexcel/Worksheet.cs @@ -8,12 +8,6 @@ namespace Simplexcel; /// public sealed partial class Worksheet { - private readonly CellCollection _cells = new CellCollection(); - - private readonly ColumnWidthCollection _columnWidth = new ColumnWidthCollection(); - - private readonly PageSetup _pageSetup = new PageSetup(); - private List _sheetViews; private List _rowBreaks; private List _columnBreaks; @@ -24,7 +18,7 @@ public sealed partial class Worksheet /// /// These chars are not part of the ECMA-376 standard, but imposed by Excel /// - public static readonly char[] InvalidSheetNameChars = new[] { '\\', '/', '?', '*', '[', ']' }; + public static readonly char[] InvalidSheetNameChars = ['\\', '/', '?', '*', '[', ']']; /// /// Get the maximum allowable length for a sheet name @@ -68,26 +62,17 @@ public Worksheet(string name) /// /// The Page Orientation and some other related values /// - public PageSetup PageSetup - { - get { return _pageSetup; } - } + public PageSetup PageSetup { get; } = new(); /// /// The Cells of the Worksheet (zero based, [0,0] = A1) /// - public CellCollection Cells - { - get { return _cells; } - } + public CellCollection Cells { get; } = new(); /// /// The Width of individual columns (Zero-based, in Excel's Units) /// - public ColumnWidthCollection ColumnWidths - { - get { return _columnWidth; } - } + public ColumnWidthCollection ColumnWidths { get; } = new(); /// /// How to handle numbers that are larger than or smaller than ? @@ -109,8 +94,8 @@ public ColumnWidthCollection ColumnWidths /// The Cell, or NULL of the Cell hasn't been created yet. public Cell this[string address] { - get { return Cells[address]; } - set { Cells[address] = value; } + get => Cells[address]; + set => Cells[address] = value; } /// @@ -121,17 +106,17 @@ public Cell this[string address] /// The Cell, or NULL of the Cell hasn't been created yet. public Cell this[int row, int column] { - get { return Cells[row, column]; } - set { Cells[row, column] = value; } + get => Cells[row, column]; + set => Cells[row, column] = value; } /// - /// Freeze the top row, that is, create a that splits the first row (A) into a pane. + /// Freeze the top row, that is, create a that splits the first row (A) into a pane. /// public void FreezeTopRow() => FreezeTopLeft(1, 0, Panes.BottomLeft); /// - /// Freeze the first column, that is, create a that splits the first column (1) into a pane. + /// Freeze the first column, that is, create a that splits the first column (1) into a pane. /// public void FreezeLeftColumn() => FreezeTopLeft(0, 1, Panes.TopRight); @@ -185,8 +170,8 @@ public void FreezeTopLeft(int rows, int columns, Panes? activePane = null) Pane = new Pane { ActivePane = activePane.Value, - XSplit = columns > 0 ? (int?)columns : null, - YSplit = rows > 0 ? (int?)rows : null, + XSplit = columns > 0 ? columns : null, + YSplit = rows > 0 ? rows : null, TopLeftCell = CellAddressHelper.ColRowToReference(columns, rows), State = PaneState.Frozen } @@ -197,10 +182,7 @@ public void FreezeTopLeft(int rows, int columns, Panes? activePane = null) private void AddSheetView(SheetView sheetView) { - if (_sheetViews == null) - { - _sheetViews = new List(); - } + _sheetViews ??= []; _sheetViews.Add(sheetView); } @@ -225,10 +207,7 @@ internal ICollection GetColumnBreaks() /// The zero-based row index (e.g., 0 for the first row) public void InsertManualPageBreakAfterRow(int row) { - if (_rowBreaks == null) - { - _rowBreaks = new List(); - } + _rowBreaks ??= []; _rowBreaks.Add(new PageBreak { @@ -243,10 +222,7 @@ public void InsertManualPageBreakAfterRow(int row) /// The zero-based index of the column (e.g., 0 for column A) public void InsertManualPageBreakAfterColumn(int col) { - if (_columnBreaks == null) - { - _columnBreaks = new List(); - } + _columnBreaks ??= []; _columnBreaks.Add(new PageBreak { diff --git a/src/Simplexcel/WorksheetView/SheetView.cs b/src/Simplexcel/WorksheetView/SheetView.cs index 217e02e..4797f01 100644 --- a/src/Simplexcel/WorksheetView/SheetView.cs +++ b/src/Simplexcel/WorksheetView/SheetView.cs @@ -8,7 +8,7 @@ namespace Simplexcel; /// the workbook, each sheet view corresponds to a separate window within the spreadsheet application, where /// each window is showing the particular sheet containing the same workbookViewId value, the last sheetView /// definition is loaded, and the others are discarded. -/// +/// /// When multiple windows are viewing the same sheet, multiple /// sheetView elements (with corresponding workbookView entries) are saved. /// @@ -16,10 +16,7 @@ public sealed class SheetView { private List _selections; - internal ICollection Selections - { - get { return _selections; } - } + internal ICollection Selections => _selections; /// /// Flag indicating whether this sheet is selected. @@ -37,7 +34,7 @@ internal ICollection Selections /// /// Zero-based index of this workbook view, pointing to a workbookView element in the bookViews collection. /// - public int WorkbookViewId { get { return 0; } } + public int WorkbookViewId => 0; /// /// The pane that this SheetView applies to @@ -58,10 +55,7 @@ public void AddSelection(Selection sel, bool throwOnDuplicatePane = true) } // Up to 4 Selections, and the Selection Pane cannot already be in use - if (_selections == null) - { - _selections = new List(4); - } + _selections ??= new List(4); foreach (var s in _selections) { @@ -71,11 +65,9 @@ public void AddSelection(Selection sel, bool throwOnDuplicatePane = true) { throw new InvalidOperationException("There is already a Selection for ActivePane " + sel.ActivePane); } - else - { - _selections.Remove(s); - break; - } + + _selections.Remove(s); + break; } } diff --git a/src/Simplexcel/XlsxInternal/Relationship.cs b/src/Simplexcel/XlsxInternal/Relationship.cs index 107a5ca..0256a9e 100644 --- a/src/Simplexcel/XlsxInternal/Relationship.cs +++ b/src/Simplexcel/XlsxInternal/Relationship.cs @@ -3,14 +3,9 @@ /// /// A Relationship inside the Package /// -internal class Relationship +internal class Relationship(RelationshipCounter counter) { - public string Id { get; set; } + public string Id { get; set; } = "r" + counter.GetNextId(); public XmlFile Target { get; set; } public string Type { get; set; } - - public Relationship(RelationshipCounter counter) - { - Id = "r" + counter.GetNextId(); - } } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/SharedStrings.cs b/src/Simplexcel/XlsxInternal/SharedStrings.cs index 78cf4a9..49b9bd6 100644 --- a/src/Simplexcel/XlsxInternal/SharedStrings.cs +++ b/src/Simplexcel/XlsxInternal/SharedStrings.cs @@ -1,9 +1,7 @@ using System; -using System.Diagnostics; using System.Linq; using System.Collections.Generic; using System.Xml.Linq; -using System.Text.RegularExpressions; namespace Simplexcel.XlsxInternal; @@ -12,12 +10,13 @@ namespace Simplexcel.XlsxInternal; /// internal class SharedStrings { - private readonly Dictionary _sharedStrings = new Dictionary(StringComparer.Ordinal); + private readonly Dictionary _sharedStrings = new(StringComparer.Ordinal); /// /// The number of Unique Strings /// - internal int UniqueCount { get { return _sharedStrings.Count; } } + internal int UniqueCount => _sharedStrings.Count; + /// /// The number of total Strings (incl. duplicate uses of the same string) /// @@ -31,10 +30,7 @@ internal class SharedStrings internal int GetStringIndex(string input) { // NULL is treated as an empty string - if (input == null) - { - input = string.Empty; - } + input ??= string.Empty; Count++; if (!_sharedStrings.ContainsKey(input)) diff --git a/src/Simplexcel/XlsxInternal/StyleWriter.cs b/src/Simplexcel/XlsxInternal/StyleWriter.cs index c7384ed..8e9d87f 100644 --- a/src/Simplexcel/XlsxInternal/StyleWriter.cs +++ b/src/Simplexcel/XlsxInternal/StyleWriter.cs @@ -12,7 +12,7 @@ internal static class StyleWriter /// /// Standard format codes as defined in ECMA-376, 3rd Edition, Part 1, 18.8.30 numFmt (Number Format) /// - private static Dictionary StandardFormatIds = new Dictionary + private static readonly Dictionary StandardFormatIds = new() { ["General"] = 0, ["0"] = 1, @@ -53,9 +53,9 @@ internal static XmlFile CreateStyleXml(IList styles) { var numberFormats = new List(); var uniqueBorders = new List { CellBorder.None }; - var uniqueFonts = new List { new XlsxFont() }; + var uniqueFonts = new List { new() }; // These two fills MUST exist as fill 0 (None) and 1 (Gray125) - var uniqueFills = new List { new PatternFill { PatternType = PatternType.None }, new PatternFill { PatternType = PatternType.Gray125 } }; + var uniqueFills = new List { new() { PatternType = PatternType.None }, new() { PatternType = PatternType.Gray125 } }; foreach (var style in styles) { @@ -276,28 +276,28 @@ private static void StyleAddFillsElement(XDocument doc, List unique if (fill is PatternFill pf) { var pfe = new XElement(Namespaces.workbook + "patternFill"); - string patternType = "none"; - switch (pf.PatternType) + var patternType = pf.PatternType switch { - case PatternType.Solid: patternType = "solid"; break; - case PatternType.Gray750: patternType = "darkGray"; break; - case PatternType.Gray500: patternType = "mediumGray"; break; - case PatternType.Gray250: patternType = "lightGray"; break; - case PatternType.Gray125: patternType = "gray125"; break; - case PatternType.Gray0625: patternType = "gray0625"; break; - case PatternType.HorizontalStripe: patternType = "darkHorizontal"; break; - case PatternType.VerticalStripe: patternType = "darkVertical"; break; - case PatternType.ReverseDiagonalStripe: patternType = "darkDown"; break; - case PatternType.DiagonalStripe: patternType = "darkUp"; break; - case PatternType.DiagonalCrosshatch: patternType = "darkGrid"; break; - case PatternType.ThickDiagonalCrosshatch: patternType = "darkTrellis"; break; - case PatternType.ThinHorizontalStripe: patternType = "lightHorizontal"; break; - case PatternType.ThinVerticalStripe: patternType = "lightVertical"; break; - case PatternType.ThinReverseDiagonalStripe: patternType = "lightDown"; break; - case PatternType.ThinDiagonalStripe: patternType = "lightUp"; break; - case PatternType.ThinHorizontalCrosshatch: patternType = "lightGrid"; break; - case PatternType.ThinDiagonalCrosshatch: patternType = "lightTrellis"; break; - } + PatternType.Solid => "solid", + PatternType.Gray750 => "darkGray", + PatternType.Gray500 => "mediumGray", + PatternType.Gray250 => "lightGray", + PatternType.Gray125 => "gray125", + PatternType.Gray0625 => "gray0625", + PatternType.HorizontalStripe => "darkHorizontal", + PatternType.VerticalStripe => "darkVertical", + PatternType.ReverseDiagonalStripe => "darkDown", + PatternType.DiagonalStripe => "darkUp", + PatternType.DiagonalCrosshatch => "darkGrid", + PatternType.ThickDiagonalCrosshatch => "darkTrellis", + PatternType.ThinHorizontalStripe => "lightHorizontal", + PatternType.ThinVerticalStripe => "lightVertical", + PatternType.ThinReverseDiagonalStripe => "lightDown", + PatternType.ThinDiagonalStripe => "lightUp", + PatternType.ThinHorizontalCrosshatch => "lightGrid", + PatternType.ThinDiagonalCrosshatch => "lightTrellis", + _ => "none" + }; pfe.Add(new XAttribute("patternType", patternType)); if (pf.BackgroundColor.HasValue) diff --git a/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs b/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs index 2cf4509..70cb664 100644 --- a/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs +++ b/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs @@ -7,13 +7,7 @@ namespace Simplexcel.XlsxInternal; /// internal class XlsxCellStyle : IEquatable { - internal XlsxCellStyle() - { - Font = new XlsxFont(); - Fill = new PatternFill(); - } - - internal XlsxFont Font { get; set; } + internal XlsxFont Font { get; set; } = new(); internal CellBorder Border { get; set; } @@ -23,7 +17,7 @@ internal XlsxCellStyle() internal HorizontalAlign HorizontalAlignment { get; set; } - internal PatternFill Fill { get; set; } + internal PatternFill Fill { get; set; } = new(); /// /// Compare this to another diff --git a/src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs b/src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs index e590e81..d4c07c6 100644 --- a/src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs +++ b/src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs @@ -5,22 +5,13 @@ namespace Simplexcel.XlsxInternal; internal sealed class XlsxIgnoredError { - private readonly IgnoredError _ignoredError; - internal HashSet Cells { get; } - - public XlsxIgnoredError() - { - Cells = new HashSet(); - _ignoredError = new IgnoredError(); - } + private readonly IgnoredError _ignoredError = new(); + internal HashSet Cells { get; } = []; internal IgnoredError IgnoredError { - get - { - // Note: This is a mutable reference, but changing it would be... bad. - return _ignoredError; - } + // Note: This is a mutable reference, but changing it would be... bad. + get => _ignoredError; set { if (value == null) diff --git a/src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs b/src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs index 94177fa..7e628d4 100644 --- a/src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs +++ b/src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs @@ -4,12 +4,7 @@ namespace Simplexcel.XlsxInternal; internal sealed class XlsxIgnoredErrorCollection { - internal IList DistinctIgnoredErrors { get; } - - public XlsxIgnoredErrorCollection() - { - DistinctIgnoredErrors = new List(); - } + internal IList DistinctIgnoredErrors { get; } = []; public void AddIgnoredError(CellAddress cellAddress, IgnoredError ignoredErrors) { diff --git a/src/Simplexcel/XlsxInternal/XlsxPackage.cs b/src/Simplexcel/XlsxInternal/XlsxPackage.cs index 9e63e0f..986eb58 100644 --- a/src/Simplexcel/XlsxInternal/XlsxPackage.cs +++ b/src/Simplexcel/XlsxInternal/XlsxPackage.cs @@ -14,25 +14,17 @@ internal class XlsxPackage /// /// All XML Files in this package /// - internal IList XmlFiles { get; } + internal IList XmlFiles { get; } = []; /// /// Package-level relationships (/_rels/.rels) /// - internal IList Relationships { get; } + internal IList Relationships { get; } = []; /// /// Workbook-level relationships (/xl/_rels/workbook.xml.rels) /// - internal IList WorkbookRelationships { get; } - - - internal XlsxPackage() - { - Relationships = new List(); - WorkbookRelationships = new List(); - XmlFiles = new List(); - } + internal IList WorkbookRelationships { get; } = []; /// /// Save the Xlsx Package to a new Stream (that the caller owns and has to dispose) @@ -40,7 +32,7 @@ internal XlsxPackage() /// internal void SaveToStream(Stream outputStream, bool compress) { - if(outputStream == null || !outputStream.CanWrite || !outputStream.CanSeek) + if (outputStream is not { CanWrite: true } || !outputStream.CanSeek) { throw new InvalidOperationException("Stream to save to must be writeable and seekable."); } @@ -68,7 +60,7 @@ internal void SaveToStream(Stream outputStream, bool compress) outputStream.Seek(0, SeekOrigin.Begin); } - private void WriteInfoXmlFile(ZipPackage pkg) + private static void WriteInfoXmlFile(ZipPackage pkg) { var infoXml = new XmlFile { diff --git a/src/Simplexcel/XlsxInternal/XlsxRow.cs b/src/Simplexcel/XlsxInternal/XlsxRow.cs index 5bdc1c8..bef5fa9 100644 --- a/src/Simplexcel/XlsxInternal/XlsxRow.cs +++ b/src/Simplexcel/XlsxInternal/XlsxRow.cs @@ -10,7 +10,7 @@ internal class XlsxRow /// /// The c elements /// - internal IList Cells { get; set; } + internal IList Cells { get; set; } = []; /// /// This implicitly sets customHeight to 1 and ht to the value @@ -21,9 +21,4 @@ internal class XlsxRow /// r (Row Index) Row index. Indicates to which row in the sheet this row definition corresponds. /// internal int RowIndex { get; set; } - - public XlsxRow() - { - Cells = new List(); - } } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/XlsxWriter.cs b/src/Simplexcel/XlsxInternal/XlsxWriter.cs index 006939b..47dd6b2 100644 --- a/src/Simplexcel/XlsxInternal/XlsxWriter.cs +++ b/src/Simplexcel/XlsxInternal/XlsxWriter.cs @@ -25,7 +25,7 @@ internal static void Save(Workbook workbook, Stream outputStream, bool compress) /// /// This does the actual writing by manually creating the XML according to ECMA-376, 3rd Edition, Part 1's SpreadsheetML. - /// + /// /// Note that in many cases, the order of elements in an XML file matters! /// private static class XlsxWriterInternal @@ -318,7 +318,7 @@ private static void WriteSheetViews(Worksheet sheet, XDocument doc) sheetView.Add(paneElem); } - foreach (var sel in sv.Selections ?? Enumerable.Empty()) + foreach (var sel in sv.Selections ?? []) { var selElem = new XElement(Namespaces.workbook + "selection"); if (!string.IsNullOrEmpty(sel.ActiveCell)) @@ -372,14 +372,14 @@ static XElement PageBreakCollectionToXml(string elementName, ICollection 0) + if (rowBreaks is { Count: > 0 }) { var rowBreaksElem = PageBreakCollectionToXml("rowBreaks", rowBreaks); doc.Root.Add(rowBreaksElem); } var colBreaks = sheet.GetColumnBreaks(); - if (colBreaks != null && colBreaks.Count > 0) + if (colBreaks is { Count: > 0 }) { var colBreaksElem = PageBreakCollectionToXml("colBreaks", colBreaks); doc.Root.Add(colBreaksElem); @@ -657,7 +657,7 @@ private static Dictionary GetXlsxRows(Worksheet sheet, IList /// The Content Type of the file (default: application/xml) /// - internal string ContentType { get; set; } + internal string ContentType { get; set; } = "application/xml"; /// /// The actual file content /// internal XDocument Content { get; set; } - - public XmlFile() - { - ContentType = "application/xml"; - } } \ No newline at end of file diff --git a/src/Simplexcel/XlsxInternal/ZipPackage.cs b/src/Simplexcel/XlsxInternal/ZipPackage.cs index d337db2..f8a384d 100644 --- a/src/Simplexcel/XlsxInternal/ZipPackage.cs +++ b/src/Simplexcel/XlsxInternal/ZipPackage.cs @@ -10,8 +10,8 @@ namespace Simplexcel.XlsxInternal; internal sealed class ZipPackage : IDisposable { private readonly ZipArchive _archive; - private readonly IDictionary _contentTypes; - private readonly IList _relationships; + private readonly IDictionary _contentTypes = new Dictionary(); + private readonly IList _relationships = []; private bool _closed; private readonly bool _compress; @@ -23,8 +23,6 @@ internal ZipPackage(Stream underlyingStream, bool useCompression) } _archive = new ZipArchive(underlyingStream, ZipArchiveMode.Create, true); - _contentTypes = new Dictionary(); - _relationships = new List(); _compress = useCompression; } From 923f2b373e41d7f6d88f8cae3f1a43a794d827e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 18 Dec 2024 10:09:56 +0100 Subject: [PATCH 06/12] Use Microsoft.Bcl.HashCode for computing has codes Also, Simplexcel now targets .NET Framework 4.6.2 instead of 4.5 (in addition to .NET Standard 2.0 and .NET Standard 2.1) --- src/Simplexcel/Cells/GradientFill.cs | 7 ++++--- src/Simplexcel/Cells/GradientStop.cs | 10 +--------- src/Simplexcel/Cells/PatternFill.cs | 17 +---------------- src/Simplexcel/ExtensionMethods.Internal.cs | 14 -------------- src/Simplexcel/Simplexcel.csproj | 20 +++++++++++--------- src/Simplexcel/XlsxInternal/XlsxCellStyle.cs | 14 +------------- src/Simplexcel/XlsxInternal/XlsxFont.cs | 14 +------------- 7 files changed, 19 insertions(+), 77 deletions(-) diff --git a/src/Simplexcel/Cells/GradientFill.cs b/src/Simplexcel/Cells/GradientFill.cs index 71d6da4..6ad95ac 100644 --- a/src/Simplexcel/Cells/GradientFill.cs +++ b/src/Simplexcel/Cells/GradientFill.cs @@ -26,11 +26,12 @@ public bool Equals(GradientFill other) public override int GetHashCode() { - unchecked + var hashCode = new HashCode(); + foreach (var stop in Stops) { - int result = Stops.GetCollectionHashCode(); - return result; + hashCode.Add(stop); } + return hashCode.ToHashCode(); } public static bool operator ==(GradientFill left, GradientFill right) diff --git a/src/Simplexcel/Cells/GradientStop.cs b/src/Simplexcel/Cells/GradientStop.cs index dad3d6e..dfc2b8a 100644 --- a/src/Simplexcel/Cells/GradientStop.cs +++ b/src/Simplexcel/Cells/GradientStop.cs @@ -30,15 +30,7 @@ public bool Equals(GradientStop other) && Equals(other.Color, Color); } - public override int GetHashCode() - { - unchecked - { - int result = Position.GetHashCode(); - result = (result * 397) ^ Color.GetHashCode(); - return result; - } - } + public override int GetHashCode() => HashCode.Combine(Position, Color); public static bool operator ==(GradientStop left, GradientStop right) { diff --git a/src/Simplexcel/Cells/PatternFill.cs b/src/Simplexcel/Cells/PatternFill.cs index 6e0beea..99bce15 100644 --- a/src/Simplexcel/Cells/PatternFill.cs +++ b/src/Simplexcel/Cells/PatternFill.cs @@ -65,22 +65,7 @@ public bool Equals(PatternFill other) } /// - public override int GetHashCode() - { - unchecked - { - int result = PatternType.GetHashCode(); - if (PatternColor.HasValue) - { - result = (result * 397) ^ PatternColor.GetHashCode(); - } - if (BackgroundColor.HasValue) - { - result = (result * 397) ^ BackgroundColor.GetHashCode(); - } - return result; - } - } + public override int GetHashCode() => HashCode.Combine(PatternType, PatternColor, BackgroundColor); /// /// Check whether the objects and are Equal. diff --git a/src/Simplexcel/ExtensionMethods.Internal.cs b/src/Simplexcel/ExtensionMethods.Internal.cs index f85d76d..9608035 100644 --- a/src/Simplexcel/ExtensionMethods.Internal.cs +++ b/src/Simplexcel/ExtensionMethods.Internal.cs @@ -33,20 +33,6 @@ private static IEnumerable GetAllForType(TypeInfo typeInfo, Func(this IEnumerable input) - { - var hashCode = 0; - if (input != null) - { - foreach (var item in input) - { - var itemHashCode = item == null ? 0 : item.GetHashCode(); - hashCode = (hashCode * 397) ^ itemHashCode; - } - } - return hashCode; - } - private static readonly char[] HexEncodingTable = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; /// diff --git a/src/Simplexcel/Simplexcel.csproj b/src/Simplexcel/Simplexcel.csproj index 2b8f06d..770c41f 100644 --- a/src/Simplexcel/Simplexcel.csproj +++ b/src/Simplexcel/Simplexcel.csproj @@ -1,6 +1,6 @@ - netstandard2.0;netstandard2.1;net45 + netstandard2.0;netstandard2.1;net462 3.0.0 Simplexcel Michael Stum <opensource@stum.de> @@ -36,19 +36,21 @@ snupkg - - - - + + + + + + + + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs b/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs index 70cb664..9735c51 100644 --- a/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs +++ b/src/Simplexcel/XlsxInternal/XlsxCellStyle.cs @@ -51,19 +51,7 @@ public bool Equals(XlsxCellStyle other) ; } - public override int GetHashCode() - { - unchecked - { - int result = Border.GetHashCode(); - result = (result * 397) ^ Font.GetHashCode(); - result = (result * 397) ^ Format.GetHashCode(); - result = (result * 397) ^ VerticalAlignment.GetHashCode(); - result = (result * 397) ^ HorizontalAlignment.GetHashCode(); - result = (result * 397) ^ Fill.GetHashCode(); - return result; - } - } + public override int GetHashCode() => HashCode.Combine(Border, Font, Format, VerticalAlignment, HorizontalAlignment, Fill); /// /// Compare a to another diff --git a/src/Simplexcel/XlsxInternal/XlsxFont.cs b/src/Simplexcel/XlsxInternal/XlsxFont.cs index 1337df5..aaf47d4 100644 --- a/src/Simplexcel/XlsxInternal/XlsxFont.cs +++ b/src/Simplexcel/XlsxInternal/XlsxFont.cs @@ -37,19 +37,7 @@ public bool Equals(XlsxFont other) && other.TextColor.Equals(TextColor); } - public override int GetHashCode() - { - unchecked - { - int result = (Name != null ? Name.GetHashCode() : 0); - result = (result*397) ^ Size; - result = (result*397) ^ Bold.GetHashCode(); - result = (result*397) ^ Italic.GetHashCode(); - result = (result*397) ^ Underline.GetHashCode(); - result = (result*397) ^ TextColor.GetHashCode(); - return result; - } - } + public override int GetHashCode() => HashCode.Combine(Name, Size, Bold, Italic, Underline, TextColor); public static bool operator ==(XlsxFont left, XlsxFont right) { From 3665a82768b259be9651d050749837d4cefadf2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 7 May 2025 19:46:45 +0200 Subject: [PATCH 07/12] Update test dependencies --- src/Simplexcel.Tests/Simplexcel.Tests.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Simplexcel.Tests/Simplexcel.Tests.csproj b/src/Simplexcel.Tests/Simplexcel.Tests.csproj index 9bd0cac..fef64b1 100644 --- a/src/Simplexcel.Tests/Simplexcel.Tests.csproj +++ b/src/Simplexcel.Tests/Simplexcel.Tests.csproj @@ -8,9 +8,9 @@ - - - + + + From 6eea2b443bf1ac02b922716eebe7c87bf737f29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 7 May 2025 23:43:22 +0200 Subject: [PATCH 08/12] Update the README * Update the README with current information * Move the release notes into its own CHANGELOG.md file * Link to usage, changelog and license files * Update sample code with link to https://github.com/0xced/Simplexcel instead of https://github.com/mstum/Simplexcel --- CHANGELOG.md | 89 ++++++++++++++++++++ LICENSE.txt | 4 +- README.md | 121 +++------------------------- USAGE.md | 2 +- src/Simplexcel.MvcTestApp/Sample.cs | 2 +- src/Simplexcel.TestApp/Program.cs | 2 +- 6 files changed, 105 insertions(+), 115 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6d783b0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,89 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 3.1.0 (2022-04-30) +* Support for Standard built-in number formats, which localize properly (PR [#36](https://github.com/mstum/Simplexcel/pull/36) and [#37](https://github.com/mstum/Simplexcel/pull/37)) +* Support for [AutoFilter](https://support.microsoft.com/en-us/office/use-autofilter-to-filter-your-data-7d87d63e-ebd0-424b-8106-e2ab61133d92) (PR [#35](https://github.com/mstum/Simplexcel/pull/35)) +* Thanks to [0xced](https://github.com/0xced) for these! + +## 3.0.2 (2020-12-25) +* Add [`SourceLink`](https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink) support + +## 3.0.1 (2019-11-20) +* Fix `TypeInitializationException` in SimplexcelVersion in some contexts (e.g., UWP, Xamarin) ([Issue #30](https://github.com/mstum/Simplexcel/issues/30)) + +## 3.0.0 (2019-11-08) +* Remove targeting netstandard1.3, add targeting for netstandard2.1 +* The library is now signed and strongly named for improved compatibility + * The AssemblyVersion is `3.0.0.0` and will not change in the future + * The AssemblyFileVersion and AssemblyInformationalVersion will contain the actual version number + * The actual signing key is checked in as [`simplexcel_oss.snk`](src/simplexcel_oss.snk), and a dump of the public key and token is in [`simplexcel_oss.txt`](src/simplexcel_oss.txt) + * There is a static `SimplexcelVersion` class with some helpers: + * PublicKeyToken is the public key token for the assembly (e.g., 65e777c740a5d92a) + * PublicKey is the full public key token for the assembly (e.g., 0024000004800000940000000602000000240000525341310...) + * VersionString is the AssemblyInformationalVersion as a string, which may include a suffix if it's a development version (e.g., 2.3.0.177-v3-dev) + * Version is the AssemblyFileVersion as a Version object, which does include any suffix (e.g., 2.3.0.177) +* No functional changes, just making sure that this is independent of future changes + +## 2.3.0 (2019-11-02) +* Add `Worksheet.FreezeTopLeft` method (by @bcopeland in PR #26) to freeze more than just the top row/left column +* Support for Formulas, for example: `sheet.Cells["B4"] = Cell.Formula("MEDIAN(A:A)");`. See [the test app](https://github.com/mstum/Simplexcel/blob/0e22dddfcb26b9672ba3ccab6d229da7535127e7/src/Simplexcel.TestApp/Program.cs#L167) for some examples + +## 2.2.1 (2018-09-19) +* Fixed bug where Background Color wasn't correctly applied to a Fill. ([Issue 23](https://github.com/mstum/Simplexcel/issues/23)) + +## 2.2.0 (2018-02-24) +* Add `IgnoredErrors` to a `Cell`, to disable Excel warnings (like "Number stored as text"). +* If `LargeNumberHandlingMode.StoreAsText` is set on a sheet, the "Number stored as Text" warning is automatically disabled for that cell. +* Add `Cell.Fill` property, which allows setting the Fill of the cell, including the background color, pattern type (diagonal, crosshatch, grid, etc.) and pattern color +* Add `netstandard2.0` version, on top of the existing `netstandard1.3` and `net45` versions. + +## 2.1.0 (2017-09-25) +* **Functional Change:** Numbers with more than 11 digits are forced as Text by Default, because [of a limitation in Excel](https://support.microsoft.com/en-us/help/2643223/long-numbers-are-displayed-incorrectly-in-excel). To restore the previous functionality, you can set `Worksheet.LargeNumberHandlingMode` to `LargeNumberHandlingMode.None`. You can also use `Cell.IsLargeNumber` to check if a given number would be affected by this. +* **Functional Change:** `Worksheet.Populate`/`Worksheet.FromData` now also reads properties from base classes. +* `Worksheet.Populate`/`Worksheet.FromData` accept a new argument, `cacheTypeColumns` which defaults to false. If set to true, then Simplexcel will cache the Reflection-based lookup of object properties. This is useful for if you have a few types that you create sheets from a lot. +* You can add `[XlsxColumn]` to a Property so that `Worksheet.Populate`/`Worksheet.FromData` can set the column name and a given column order. *Caveat:* If you set `ColumnIndex` on some, but not all Properties, the properties without a `ColumnIndex` will be on the right of the last assigned column, even if that means gaps. I recommend that you either set `ColumnIndex` on all properties or none. +* You can add `[XlsxIgnoreColumn]` to a Property so that `Worksheet.Populate`/`Worksheet.FromData` ignores it. +* Added `Cell.HorizontalAlignment` and `Cell.VerticalAlignment` to allow setting the alignment of a cell (left/center/right/justify, top/middle/bottom/justify). +* Added XmlDoc to Nuget package, so you should get Intellisense with proper comments now. + +## 2.0.5 (2017-09-23) +* Add support for manual page breaks. Call `Worksheet.InsertManualPageBreakAfterRow` or `Worksheet.InsertManualPageBreakAfterColumn` with either the zero-based index of the row/column after which to create the break, or with a cell address (e.g., B5) to create the break below or to the left of that cell. + +## 2.0.4 (2017-09-17) +* Support for [freezing panes](https://support.office.com/en-us/article/Freeze-panes-to-lock-rows-and-columns-dab2ffc9-020d-4026-8121-67dd25f2508f). Right now, this is being kept simple: call either `Worksheet.FreezeTopRow` or `Worksheet.FreezeLeftColumn` to freeze either the first row (1) or the leftmost column (A). +* If a Stream is not seekable (e.g., HttpContext.Response.OutputStream), Simplexcel automatically creates a temporary MemoryStream as an intermediate. +* Add `Cell.FromObject` to make Cell creation easier by guessing the correct type. +* Support `DateTime` cells, thanks to @mguinness and PR #16. + +## 2.0.3 (2017-09-08) +* Add `Worksheet.Populate` method to fill a sheet with data. Caveats: Does not loot at inherited members, doesn't look at complex types. +* Also add static `Worksheet.FromData` method to create and populate the sheet in one. + +## 2.0.2 (2017-06-17) +* Add additional validation when saving to a Stream. The stream must be seekable (and of course writeable), otherwise an Exception is thrown. + +## 2.0.1 (2017-05-18) +* Fix [Issue #12](https://github.com/mstum/Simplexcel/issues/12): Sanitizing Regex stripped out too many characters (like the Ampersand or Emojis). Note that certain Unicode characters only work on newer versions of Excel (e.g., Emojis work in Excel 2013 but not 2007 or 2010). + +## 2.0.0 (2017-04-22) +* Re-target to .net Framework 4.5 and .NET Standard 1.3. +* No longer use `System.Drawing.Color` but new type `Simplexcel.Color` should work. +* Classes no longer use Data Contract serializer, hence no more `[DataContract]`, `[DataMember]`, etc. attributes. +* Remove `CompressionLevel` - the entire creation of the actual .xlsx file is re-done (no more dependency on `System.IO.Packaging`) and compression is now a simple bool. + +## 1.0.5 (2014-01-30) +* SharedStrings are sanitized to avoid XML Errors when using Escape chars (like 0x1B). + +## 1.0.4 (2014-01-21) +* Workbook.Save throws an InvalidOperationException if there are no sheets. + +## 1.0.3 (2013-08-20) +* Added support for external hyperlinks. +* Made Workbooks serializable using the .net DataContractSerializer. + +## 1.0.2 (2013-01-10) +* Initial Public Release. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index bd7efa7..30c9be7 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) - -Copyright (c) 2013-2017 Michael Stum, http://www.Stum.de + +Copyright (c) 2013-2025 Cédric Luthi & Michael Stum, http://www.stum.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 710d410..abcaff1 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,13 @@ # Simplexcel -## This project is discontinued +[Official take over](https://github.com/mstum/Simplexcel/issues/43#issuecomment-1227796312) of the original project by Michael Stum. -Please see https://github.com/mstum/Simplexcel/issues/43 +[![NuGet](https://img.shields.io/nuget/v/Simplexcel.svg?label=NuGet&logo=NuGet)](https://www.nuget.org/packages/simplexcel) -# Old README.md for 3.1.0.195 - -| master | release | -| ------ | ------- | -| [![Actions Status](https://github.com/mstum/Simplexcel/workflows/CI%20Build/badge.svg?branch=master)](https://github.com/mstum/Simplexcel/actions) | [![Actions Status](https://github.com/mstum/Simplexcel/workflows/CI%20Build/badge.svg?branch=release)](https://github.com/mstum/Simplexcel/actions) | - -This is a simple .xlsx generator library for .net 4.5, .NET Standard 2.0, and .NET Standard 2.1. +This is a simple .xlsx generator library for .NET Framework (4.6.2 and later), .NET Standard 2.0, and .NET Standard 2.1. It does not aim to implement the entirety of the Office Open XML Workbook format and all the small and big features Excel offers. -Instead, it is meant as a way to handle common tasks that can't be handled by other workarounds (e.g., CSV Files or HTML Tables) and is fully supported under ASP.net (unlike, say, COM Interop which Microsoft explicitly doesn't support on a server). +Instead, it is meant as a way to handle common tasks that can't be handled by other workarounds (e.g., CSV Files or HTML Tables) and is fully supported under ASP.NET and ASP.NET Core (unlike, say, COM Interop which Microsoft explicitly doesn't support on a server). # Features * You can store numbers as numbers, so no more unwanted conversion to scientific notation on large numbers! @@ -22,108 +16,15 @@ Instead, it is meant as a way to handle common tasks that can't be handled by ot * You have basic formatting: Font Name/Size, Bold/Underline/Italic, Color, Border around a cell * You can specify the size of cells * Workbooks can be saved compressed or uncompressed (CPU Load vs. File Size) -* You can specify repeating rows and columns (from the top and left respectively), useful when printing. -* Fully supported in ASP.net and Windows Services -* Supports .net Core +* You can specify repeating rows and columns (from the top and left respectively), useful when printing +* Fully supported in ASP.NET, ASP.NET Core and Windows Services +* Supports both .NET Framework and .NET # Usage -See [USAGE.md](https://github.com/mstum/Simplexcel/blob/master/USAGE.md) for instructions how to use. - -# Changelog -## 3.1.0 (2022-04-30) -* Support for Standard built-in number formats, which localize properly (PR [#36](https://github.com/mstum/Simplexcel/pull/36) and [#37](https://github.com/mstum/Simplexcel/pull/37)) -* Support for [AutoFilter](https://support.microsoft.com/en-us/office/use-autofilter-to-filter-your-data-7d87d63e-ebd0-424b-8106-e2ab61133d92) (PR [#35](https://github.com/mstum/Simplexcel/pull/35)) -* Thanks to [0xced](https://github.com/0xced) for these! - -## 3.0.2 (2020-12-25) -* Add [`SourceLink`](https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink) support - -## 3.0.1 (2019-11-20) -* Fix `TypeInitializationException` in SimplexcelVersion in some contexts (e.g., UWP, Xamarin) ([Issue #30](https://github.com/mstum/Simplexcel/issues/30)) - -## 3.0.0 (2019-11-08) -* Remove targeting netstandard1.3, add targeting for netstandard2.1 -* The library is now signed and strongly named for improved compatibility - * The AssemblyVersion is `3.0.0.0` and will not change in the future - * The AssemblyFileVersion and AssemblyInformationalVersion will contain the actual version number - * The actual signing key is checked in as [`simplexcel_oss.snk`](src/simplexcel_oss.snk), and a dump of the public key and token is in [`simplexcel_oss.txt`](src/simplexcel_oss.txt) - * There is a static `SimplexcelVersion` class with some helpers: - * PublicKeyToken is the public key token for the assembly (e.g., 65e777c740a5d92a) - * PublicKey is the full public key token for the assembly (e.g., 0024000004800000940000000602000000240000525341310...) - * VersionString is the AssemblyInformationalVersion as a string, which may include a suffix if it's a development version (e.g., 2.3.0.177-v3-dev) - * Version is the AssemblyFileVersion as a Version object, which does include any suffix (e.g., 2.3.0.177) -* No functional changes, just making sure that this is independent of future changes - -## 2.3.0 (2019-11-02) -* Add `Worksheet.FreezeTopLeft` method (by @bcopeland in PR #26) to freeze more than just the top row/left column -* Support for Formulas, for example: `sheet.Cells["B4"] = Cell.Formula("MEDIAN(A:A)");`. See [the test app](https://github.com/mstum/Simplexcel/blob/0e22dddfcb26b9672ba3ccab6d229da7535127e7/src/Simplexcel.TestApp/Program.cs#L167) for some examples - -## 2.2.1 (2018-09-19) -* Fixed bug where Background Color wasn't correctly applied to a Fill. ([Issue 23](https://github.com/mstum/Simplexcel/issues/23)) - -## 2.2.0 (2018-02-24) -* Add `IgnoredErrors` to a `Cell`, to disable Excel warnings (like "Number stored as text"). -* If `LargeNumberHandlingMode.StoreAsText` is set on a sheet, the "Number stored as Text" warning is automatically disabled for that cell. -* Add `Cell.Fill` property, which allows setting the Fill of the cell, including the background color, pattern type (diagonal, crosshatch, grid, etc.) and pattern color -* Add `netstandard2.0` version, on top of the existing `netstandard1.3` and `net45` versions. - -## 2.1.0 (2017-09-25) -* **Functional Change:** Numbers with more than 11 digits are forced as Text by Default, because [of a limitation in Excel](https://support.microsoft.com/en-us/help/2643223/long-numbers-are-displayed-incorrectly-in-excel). To restore the previous functionality, you can set `Worksheet.LargeNumberHandlingMode` to `LargeNumberHandlingMode.None`. You can also use `Cell.IsLargeNumber` to check if a given number would be affected by this. -* **Functional Change:** `Worksheet.Populate`/`Worksheet.FromData` now also reads properties from base classes. -* `Worksheet.Populate`/`Worksheet.FromData` accept a new argument, `cacheTypeColumns` which defaults to false. If set to true, then Simplexcel will cache the Reflection-based lookup of object properties. This is useful for if you have a few types that you create sheets from a lot. -* You can add `[XlsxColumn]` to a Property so that `Worksheet.Populate`/`Worksheet.FromData` can set the column name and a given column order. *Caveat:* If you set `ColumnIndex` on some, but not all Properties, the properties without a `ColumnIndex` will be on the right of the last assigned column, even if that means gaps. I recommend that you either set `ColumnIndex` on all properties or none. -* You can add `[XlsxIgnoreColumn]` to a Property so that `Worksheet.Populate`/`Worksheet.FromData` ignores it. -* Added `Cell.HorizontalAlignment` and `Cell.VerticalAlignment` to allow setting the alignment of a cell (left/center/right/justify, top/middle/bottom/justify). -* Added XmlDoc to Nuget package, so you should get Intellisense with proper comments now. - -## 2.0.5 (2017-09-23) -* Add support for manual page breaks. Call `Worksheet.InsertManualPageBreakAfterRow` or `Worksheet.InsertManualPageBreakAfterColumn` with either the zero-based index of the row/column after which to create the break, or with a cell address (e.g., B5) to create the break below or to the left of that cell. - -## 2.0.4 (2017-09-17) -* Support for [freezing panes](https://support.office.com/en-us/article/Freeze-panes-to-lock-rows-and-columns-dab2ffc9-020d-4026-8121-67dd25f2508f). Right now, this is being kept simple: call either `Worksheet.FreezeTopRow` or `Worksheet.FreezeLeftColumn` to freeze either the first row (1) or the leftmost column (A). -* If a Stream is not seekable (e.g., HttpContext.Response.OutputStream), Simplexcel automatically creates a temporary MemoryStream as an intermediate. -* Add `Cell.FromObject` to make Cell creation easier by guessing the correct type. -* Support `DateTime` cells, thanks to @mguinness and PR #16. +See [USAGE.md](USAGE.md) for instructions how to use. -## 2.0.3 (2017-09-08) -* Add `Worksheet.Populate` method to fill a sheet with data. Caveats: Does not loot at inherited members, doesn't look at complex types. -* Also add static `Worksheet.FromData` method to create and populate the sheet in one. - -## 2.0.2 (2017-06-17) -* Add additional validation when saving to a Stream. The stream must be seekable (and of course writeable), otherwise an Exception is thrown. - -## 2.0.1 (2017-05-18) -* Fix [Issue #12](https://github.com/mstum/Simplexcel/issues/12): Sanitizing Regex stripped out too many characters (like the Ampersand or Emojis). Note that certain Unicode characters only work on newer versions of Excel (e.g., Emojis work in Excel 2013 but not 2007 or 2010). - -## 2.0.0 (2017-04-22) -* Re-target to .net Framework 4.5 and .NET Standard 1.3. -* No longer use `System.Drawing.Color` but new type `Simplexcel.Color` should work. -* Classes no longer use Data Contract serializer, hence no more `[DataContract]`, `[DataMember]`, etc. attributes. -* Remove `CompressionLevel` - the entire creation of the actual .xlsx file is re-done (no more dependency on `System.IO.Packaging`) and compression is now a simple bool. - -## 1.0.5 (2014-01-30) -* SharedStrings are sanitized to avoid XML Errors when using Escape chars (like 0x1B). - -## 1.0.4 (2014-01-21) -* Workbook.Save throws an InvalidOperationException if there are no sheets. - -## 1.0.3 (2013-08-20) -* Added support for external hyperlinks. -* Made Workbooks serializable using the .net DataContractSerializer. - -## 1.0.2 (2013-01-10) -* Initial Public Release. +# Release Notes +See [CHANGELOG.md](CHANGELOG.md) for the release notes. # License -http://mstum.mit-license.org/ - -The MIT License (MIT) - -Copyright (c) 2013-2017 Michael Stum, http://www.Stum.de -Contains contributions by [@mguinness](https://github.com/mguinness) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +See [LICENSE.txt](LICENSE.txt) for the license (MIT). diff --git a/USAGE.md b/USAGE.md index 7c33655..4a3e42b 100644 --- a/USAGE.md +++ b/USAGE.md @@ -93,7 +93,7 @@ You can create Hyperlinks for a cell. ```cs sheet.Cells["A1"] = "Click me now!"; -sheet.Cells["A1"].Hyperlink = "https://github.com/mstum/Simplexcel/"; +sheet.Cells["A1"].Hyperlink = "https://github.com/0xced/Simplexcel/"; ``` This will NOT automatically format it as a Hyperlink (blue/underlined) to give you freedom to format as desired. diff --git a/src/Simplexcel.MvcTestApp/Sample.cs b/src/Simplexcel.MvcTestApp/Sample.cs index 3be1d8c..ed76f84 100644 --- a/src/Simplexcel.MvcTestApp/Sample.cs +++ b/src/Simplexcel.MvcTestApp/Sample.cs @@ -36,7 +36,7 @@ public static Workbook GenerateWorkbook() cell.Italic = true; cell.TextColor = Color.Blue; cell.FontSize = 18; - cell.Hyperlink = "https://github.com/mstum/Simplexcel"; + cell.Hyperlink = "https://github.com/0xced/Simplexcel"; sheet.Cells[0, 3] = cell; sheet.ColumnWidths[3] = 40; diff --git a/src/Simplexcel.TestApp/Program.cs b/src/Simplexcel.TestApp/Program.cs index 89396e4..0fe9b5e 100644 --- a/src/Simplexcel.TestApp/Program.cs +++ b/src/Simplexcel.TestApp/Program.cs @@ -47,7 +47,7 @@ static void Main() cell.Italic = true; cell.TextColor = Color.Blue; cell.FontSize = 18; - cell.Hyperlink = "https://github.com/mstum/Simplexcel"; + cell.Hyperlink = "https://github.com/0xced/Simplexcel"; sheet.Cells[0, 3] = cell; sheet.ColumnWidths[3] = 40; From 0e001a34d118616a58055a5d7c3750da22de73bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 7 May 2025 23:55:27 +0200 Subject: [PATCH 09/12] Rewrite the release process * Simplify the project properties * Use MinVer * Use package validation * Validate SourceLink with `dotnet validate` --- .config/dotnet-tools.json | 13 ++ .github/workflows/cibuild.yml | 68 ----------- .github/workflows/continuous-integration.yml | 96 +++++++++++++++ .github/workflows/test-report.yml | 25 ++++ CHANGELOG.md | 3 + MAINTENANCE.md | 28 +++++ src/Simplexcel.sln => Simplexcel.sln | 20 ++- global.json | 8 ++ .../Simplexcel.MvcTestApp.csproj | 1 + src/Simplexcel.Tests/Simplexcel.Tests.csproj | 10 +- src/Simplexcel/CompatibilitySuppressions.xml | 8 ++ src/Simplexcel/Simplexcel.csproj | 85 ++++++++----- src/Simplexcel/packages.lock.json | 115 ++++++++++++++++++ 13 files changed, 374 insertions(+), 106 deletions(-) create mode 100644 .config/dotnet-tools.json delete mode 100644 .github/workflows/cibuild.yml create mode 100644 .github/workflows/continuous-integration.yml create mode 100644 .github/workflows/test-report.yml create mode 100644 MAINTENANCE.md rename src/Simplexcel.sln => Simplexcel.sln (71%) create mode 100644 global.json create mode 100644 src/Simplexcel/CompatibilitySuppressions.xml create mode 100644 src/Simplexcel/packages.lock.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..242ebf3 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-validate": { + "version": "0.0.1-preview.537", + "commands": [ + "dotnet-validate" + ], + "rollForward": true + } + } +} \ No newline at end of file diff --git a/.github/workflows/cibuild.yml b/.github/workflows/cibuild.yml deleted file mode 100644 index 648ffe8..0000000 --- a/.github/workflows/cibuild.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: CI Build - -on: - pull_request: - push: - paths: - - "src/**" - - ".github/workflows/**" - - "scripts/**" - -# To avoid vendor lock-in, this is mostly driven by a build script -jobs: - build-linux: # Sanity check build - name: Linux - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Run build script - shell: pwsh - run: | - $Env:GIT_BRANCH=$Env:GITHUB_REF - $Env:build_artifactsDir=$Env:RUNNER_TEMP - cd scripts - ./build.ps1 - Get-ChildItem Env: | Where-Object {$_.Name -Match "^MH_"} | %{ echo "::set-output name=$($_.Name)::$($_.Value)" } - build: # Actual prod build - name: Windows - runs-on: windows-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - # To avoid vendor lock-in, this is mostly driven by a build script - - name: Run build script - id: pwshbuild - shell: pwsh - run: | - $Env:GIT_BRANCH=$Env:GITHUB_REF - $Env:build_artifactsDir=$Env:RUNNER_TEMP - cd scripts - ./build.ps1 - Get-ChildItem Env: | Where-Object {$_.Name -Match "^MH_"} | %{ echo "::set-output name=$($_.Name)::$($_.Value)" } - - name: Publish to GPR (Development) - if: success() && !startsWith(github.ref, 'refs/pull') && steps.pwshbuild.outputs.MH_IS_PROD_BUILD == 'False' - shell: pwsh - run: | - $mhPath = "${{ steps.pwshbuild.outputs.MH_BUILD_OUTPUT }}" - $nupkgName = "simplexcel.${{ steps.pwshbuild.outputs.MH_BUILD_VERSION_PACKAGE }}.nupkg" - $nupkgPath = Join-Path -Path $mhPath -ChildPath $nupkgName - - # Publish to GitHub Package Repository - Write-Host "Dev build, pushing to GPR: $nupkgPath" - dotnet nuget push "$nupkgPath" --source https://nuget.pkg.github.com/mstum --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate --no-symbols - - name: Publish to Nuget.org (Release) - if: success() && steps.pwshbuild.outputs.MH_IS_PROD_BUILD == 'True' - shell: pwsh - run: | - $mhPath = "${{ steps.pwshbuild.outputs.MH_BUILD_OUTPUT }}" - $nupkgName = "simplexcel.${{ steps.pwshbuild.outputs.MH_BUILD_VERSION_PACKAGE }}.nupkg" - $nupkgPath = Join-Path -Path $mhPath -ChildPath $nupkgName - - # Publish to Nuget.org - Write-Host "Release build, pushing to Nuget.org: $nupkgPath" - dotnet nuget push "$nupkgPath" -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json --no-symbols diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..75ed7c4 --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,96 @@ +name: Continuous Integration + +on: push + +env: + Configuration: Release + ContinuousIntegrationBuild: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: true + TERM: xterm + +jobs: + package: + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + fail-fast: false + runs-on: ${{ matrix.os }} + name: 🛠 Build, test and pack + steps: + - name: 🧑‍💻 Checkout git repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: 🏎 Optimize Windows runner + if: matrix.os == 'windows-latest' + run: | + echo "DOTNET_INSTALL_DIR=D:\dotnet" >> $env:GITHUB_ENV + echo "NUGET_PACKAGES=D:\nuget" >> $env:GITHUB_ENV + - name: 🧑‍🔧 Install .NET SDK + uses: actions/setup-dotnet@v4 + - name: ℹ️ Show .NET info + run: dotnet --info + - name: 💾 Retrieve cached NuGet packages + uses: actions/cache@v4 + with: + path: ${{ env.NUGET_PACKAGES || '~/.nuget/packages' }} + key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} + restore-keys: | + ${{ runner.os }}-nuget- + - name: ⚙️ Restore NuGet packages + run: dotnet restore + - name: 🏗 Build solution + run: dotnet build --no-restore + - name: 🧪 Run tests + run: dotnet test --no-build + - name: 📤 Upload test results + uses: actions/upload-artifact@v4 + with: + name: TestResults-${{ runner.os }}.trx + path: "*.trx" + - name: 📦 Create NuGet package + run: dotnet pack --no-build --output . + - name: 📤 Upload NuGet package artifact + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v4 + with: + name: NuGet package + path: "*.nupkg" + - name: 📝 Retrieve release notes from tag + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') + run: | + git fetch --tags --force + git tag --list ${{ github.ref_name }} --format='%(contents)' > ReleaseNotes.md + - name: 📤 Upload release notes + if: matrix.os == 'ubuntu-latest' && startsWith(github.ref, 'refs/tags/') + uses: actions/upload-artifact@v4 + with: + name: Release Notes + path: ReleaseNotes.md + publish: + runs-on: macos-latest + needs: package + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + name: 🐿 Publish + steps: + - name: 📥 Download NuGet package artifact + uses: actions/download-artifact@v4 + with: + name: NuGet package + - name: 📥 Download release notes artifact + uses: actions/download-artifact@v4 + with: + name: Release Notes + - name: 🚢 Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: Version ${{ github.ref_name }} + body_path: ReleaseNotes.md + prerelease: ${{ contains(github.ref_name, '-') }} + files: "*.nupkg" + - name: 🚀 Publish NuGet package on nuget.org + run: dotnet nuget push "*.nupkg" --source https://api.nuget.org/v3/index.json --api-key "${{ secrets.NUGET_API_KEY }}" diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml new file mode 100644 index 0000000..348633c --- /dev/null +++ b/.github/workflows/test-report.yml @@ -0,0 +1,25 @@ +name: Test Report + +on: + workflow_run: + workflows: ['Continuous Integration'] + types: + - completed + +permissions: + checks: write + +jobs: + report: + runs-on: ubuntu-latest + strategy: + matrix: + os: [ Linux, macOS, Windows ] + fail-fast: false + steps: + - uses: dorny/test-reporter@v1 + with: + artifact: TestResults-${{ matrix.os }}.trx + name: 🚦 Test Results (${{ matrix.os }}) + path: '*.trx' + reporter: dotnet-trx diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d783b0..d0cd2fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +* Drop support for .NET Framework 4.5 whose support ended on January 12th, 2016. Replaced by support for .NET Framework 4.6.2. + ## 3.1.0 (2022-04-30) * Support for Standard built-in number formats, which localize properly (PR [#36](https://github.com/mstum/Simplexcel/pull/36) and [#37](https://github.com/mstum/Simplexcel/pull/37)) * Support for [AutoFilter](https://support.microsoft.com/en-us/office/use-autofilter-to-filter-your-data-7d87d63e-ebd0-424b-8106-e2ab61133d92) (PR [#35](https://github.com/mstum/Simplexcel/pull/35)) diff --git a/MAINTENANCE.md b/MAINTENANCE.md new file mode 100644 index 0000000..4be629e --- /dev/null +++ b/MAINTENANCE.md @@ -0,0 +1,28 @@ +## Creating a release + +**Simplexcel** uses [MinVer](https://github.com/adamralph/minver) for its versioning, so a tag must exist with the chosen semantic version number in order to create an official release. + +1. Update the [CHANGELOG](CHANGELOG.md) *Unreleased* section to the chosen version and copy the release notes for step #2. + + - Add the release date + - Update the link from `HEAD` to the chosen version + +2. Create an **[annotated](https://stackoverflow.com/questions/11514075/what-is-the-difference-between-an-annotated-and-unannotated-tag/25996877#25996877)** tag, the (multi-line) message of the annotated tag will be the content of the GitHub release. Markdown (copied from step #1) should be used. + + `git tag --annotate 1.0.0-rc.1` + +3. Push the `main` branch and ensure that the [build is successful](https://github.com/0xced/simplexcel/actions). + + `git push` + +4. [Push the tag](https://stackoverflow.com/questions/5195859/how-do-you-push-a-tag-to-a-remote-repository-using-git/26438076#26438076) + + `git push --follow-tags` + +Once pushed, the GitHub [Continuous Integration](https://github.com/0xced/simplexcel/blob/main/.github/workflows/continuous-integration.yml) workflow takes care of building, running the tests, creating the NuGet package, creating the GitHub release and finally publishing the produced NuGet package. + +After the NuGet package is successfully published: + +4. Update the `PackageValidationBaselineVersion` element in the `Simplexcel.csproj` file to the newly released version. +5. Delete the `CompatibilitySuppressions.xml` file if there's one. + diff --git a/src/Simplexcel.sln b/Simplexcel.sln similarity index 71% rename from src/Simplexcel.sln rename to Simplexcel.sln index 8a29828..c6b3869 100644 --- a/src/Simplexcel.sln +++ b/Simplexcel.sln @@ -2,17 +2,24 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29424.173 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel", "Simplexcel\Simplexcel.csproj", "{39AFAE0E-34D0-43AD-ACD7-39FBE03A57F5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel", "src\Simplexcel\Simplexcel.csproj", "{39AFAE0E-34D0-43AD-ACD7-39FBE03A57F5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel.TestApp", "Simplexcel.TestApp\Simplexcel.TestApp.csproj", "{C015203B-2EA6-48D7-8571-E3EAC963A583}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel.TestApp", "src\Simplexcel.TestApp\Simplexcel.TestApp.csproj", "{C015203B-2EA6-48D7-8571-E3EAC963A583}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel.Tests", "Simplexcel.Tests\Simplexcel.Tests.csproj", "{1A651F47-91E3-40EC-93EF-DAD0326BD995}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel.Tests", "src\Simplexcel.Tests\Simplexcel.Tests.csproj", "{1A651F47-91E3-40EC-93EF-DAD0326BD995}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simplexcel.MvcTestApp", "Simplexcel.MvcTestApp\Simplexcel.MvcTestApp.csproj", "{26E384B1-4991-4B7B-B68C-4DDB500AE37C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simplexcel.MvcTestApp", "src\Simplexcel.MvcTestApp\Simplexcel.MvcTestApp.csproj", "{26E384B1-4991-4B7B-B68C-4DDB500AE37C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{12C68E3A-847B-44A1-9492-026905E2FDBA}" ProjectSection(SolutionItems) = preProject - simplexcel_oss.snk = simplexcel_oss.snk + src\simplexcel_oss.snk = src\simplexcel_oss.snk + CHANGELOG.md = CHANGELOG.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{D68F219C-C0F9-4572-AF67-EFA24C8A2C40}" + ProjectSection(SolutionItems) = preProject + .github\workflows\continuous-integration.yml = .github\workflows\continuous-integration.yml + .github\workflows\test-report.yml = .github\workflows\test-report.yml EndProjectSection EndProject Global @@ -44,4 +51,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B4AFD13E-5383-4FDE-BA21-BA7BAA4C7D7E} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D68F219C-C0F9-4572-AF67-EFA24C8A2C40} = {12C68E3A-847B-44A1-9492-026905E2FDBA} + EndGlobalSection EndGlobal diff --git a/global.json b/global.json new file mode 100644 index 0000000..d53d23e --- /dev/null +++ b/global.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/global", + "sdk": { + "version": "8.0.300", + "allowPrerelease": false, + "rollForward": "latestFeature" + } +} diff --git a/src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj b/src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj index 9c25065..f9f397d 100644 --- a/src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj +++ b/src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj @@ -3,6 +3,7 @@ net8.0 enable + false diff --git a/src/Simplexcel.Tests/Simplexcel.Tests.csproj b/src/Simplexcel.Tests/Simplexcel.Tests.csproj index fef64b1..f1ff987 100644 --- a/src/Simplexcel.Tests/Simplexcel.Tests.csproj +++ b/src/Simplexcel.Tests/Simplexcel.Tests.csproj @@ -4,7 +4,15 @@ net8.0 true ../simplexcel_oss.snk - false + + + + + + + + $([MSBuild]::NormalizePath('$(MSBuildProjectDirectory)', '..', '..')) + @(VSTestLogger) diff --git a/src/Simplexcel/CompatibilitySuppressions.xml b/src/Simplexcel/CompatibilitySuppressions.xml new file mode 100644 index 0000000..738dbc7 --- /dev/null +++ b/src/Simplexcel/CompatibilitySuppressions.xml @@ -0,0 +1,8 @@ + + + + + PKV006 + .NETFramework,Version=v4.5 + + \ No newline at end of file diff --git a/src/Simplexcel/Simplexcel.csproj b/src/Simplexcel/Simplexcel.csproj index 770c41f..28fbf38 100644 --- a/src/Simplexcel/Simplexcel.csproj +++ b/src/Simplexcel/Simplexcel.csproj @@ -1,46 +1,60 @@ + netstandard2.0;netstandard2.1;net462 - 3.0.0 - Simplexcel - Michael Stum <opensource@stum.de> - 3.0.0.0 - 3.0.0.0 - en - Simplexcel .xlsx library - A 100% managed code library to generate Excel .xlsx Workbooks. Can be safely used on a server, no COM Interop or other unsafe/unsupported operations. - © 2013-2020 Michael Stum - MIT - false - https://github.com/mstum/simplexcel - https://github.com/mstum/simplexcel.git - package.png - git - xlsx excel ooxml netcore netstandard - simplexcel - true + + + + 12 + true true ../simplexcel_oss.snk - false - 12 - - + + embedded - true + true + true + + + + Cédric Luthi,Michael Stum + © 2013-$([System.DateTime]::Now.Year) Cédric Luthi & Michael Stum + A 100% managed code library to generate Excel .xlsx Workbooks. Can be safely used on a server, no COM Interop or other unsafe/unsupported operations. + Simplexcel .xlsx library + simplexcel + package.png + README.md + MIT + xlsx excel ooxml netcore netstandard + https://github.com/0xced/simplexcel + https://github.com/0xced/simplexcel/blob/main/CHANGELOG.md true - True - true - true - true - snupkg - - - + + + - + + + 4.0 + + + + true + 3.1.0.195 + + + + true + true + + + + + + @@ -50,7 +64,14 @@ + + + + + + + diff --git a/src/Simplexcel/packages.lock.json b/src/Simplexcel/packages.lock.json new file mode 100644 index 0000000..f621d77 --- /dev/null +++ b/src/Simplexcel/packages.lock.json @@ -0,0 +1,115 @@ +{ + "version": 1, + "dependencies": { + ".NETFramework,Version=v4.6.2": { + "Microsoft.Bcl.HashCode": { + "type": "Direct", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "GI4jcoi6eC9ZhNOQylIBaWOQjyGaR8T6N3tC1u8p3EXfndLCVNNWa+Zp+ocjvvS3kNBN09Zma2HXL0ezO0dRfw==" + }, + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net462": "1.0.3" + } + }, + "MinVer": { + "type": "Direct", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "+/SsmiySsXJlvQLCGBqaZKNVt3s/Y/HbAdwtop7Km2CnuZbaScoqkWJEBQ5Cy9ebkn6kCYKrHsXgwrFdTgcb3g==" + }, + "PolySharp": { + "type": "Direct", + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" + }, + "Microsoft.NETFramework.ReferenceAssemblies.net462": { + "type": "Transitive", + "resolved": "1.0.3", + "contentHash": "IzAV30z22ESCeQfxP29oVf4qEo8fBGXLXSU6oacv/9Iqe6PzgHDKCaWfwMBak7bSJQM0F5boXWoZS+kChztRIQ==" + } + }, + ".NETStandard,Version=v2.0": { + "Microsoft.Bcl.HashCode": { + "type": "Direct", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "GI4jcoi6eC9ZhNOQylIBaWOQjyGaR8T6N3tC1u8p3EXfndLCVNNWa+Zp+ocjvvS3kNBN09Zma2HXL0ezO0dRfw==" + }, + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3" + } + }, + "MinVer": { + "type": "Direct", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "+/SsmiySsXJlvQLCGBqaZKNVt3s/Y/HbAdwtop7Km2CnuZbaScoqkWJEBQ5Cy9ebkn6kCYKrHsXgwrFdTgcb3g==" + }, + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "PolySharp": { + "type": "Direct", + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETFramework.ReferenceAssemblies.net461": { + "type": "Transitive", + "resolved": "1.0.3", + "contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA==" + } + }, + ".NETStandard,Version=v2.1": { + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net461": "1.0.3" + } + }, + "MinVer": { + "type": "Direct", + "requested": "[6.0.0, )", + "resolved": "6.0.0", + "contentHash": "+/SsmiySsXJlvQLCGBqaZKNVt3s/Y/HbAdwtop7Km2CnuZbaScoqkWJEBQ5Cy9ebkn6kCYKrHsXgwrFdTgcb3g==" + }, + "PolySharp": { + "type": "Direct", + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" + }, + "Microsoft.NETFramework.ReferenceAssemblies.net461": { + "type": "Transitive", + "resolved": "1.0.3", + "contentHash": "AmOJZwCqnOCNp6PPcf9joyogScWLtwy0M1WkqfEQ0M9nYwyDD7EX9ZjscKS5iYnyvteX7kzSKFCKt9I9dXA6mA==" + } + } + } +} \ No newline at end of file From f7d4e76febbeb318a686035e7974bd0da626b037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Thu, 8 May 2025 00:50:35 +0200 Subject: [PATCH 10/12] Always upload the trx file, even when tests fail --- .github/workflows/continuous-integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 75ed7c4..96d903a 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -47,6 +47,7 @@ jobs: run: dotnet test --no-build - name: 📤 Upload test results uses: actions/upload-artifact@v4 + if: always() with: name: TestResults-${{ runner.os }}.trx path: "*.trx" From 7e9b79a5d4d3aaeefb299873c5c1619e74c7c7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Thu, 8 May 2025 00:46:24 +0200 Subject: [PATCH 11/12] Add tests to populate a worksheet with either a class or a record --- src/Simplexcel.Tests/WorksheetTests.cs | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Simplexcel.Tests/WorksheetTests.cs b/src/Simplexcel.Tests/WorksheetTests.cs index 6938ff6..6a97183 100644 --- a/src/Simplexcel.Tests/WorksheetTests.cs +++ b/src/Simplexcel.Tests/WorksheetTests.cs @@ -48,4 +48,32 @@ public void FreezeTopLeft_NegativeColumns_Throws() var ws = new Worksheet("Foo"); Assert.Throws("columns", () => ws.FreezeTopLeft(9, -1)); } + + [Fact] + public void Worksheet_PopulatePersonClass_TwoColumns() + { + var ws = new Worksheet("Foo"); + ws.Populate([new PersonClass { FirstName = "Cédric", LastName = "Luthi" }]); + Assert.Equal(2, ws.Cells.ColumnCount); + Assert.Equal("FirstName", ws["A1"].Value); + Assert.Equal("LastName", ws["B1"].Value); + } + + [Fact] + public void Worksheet_PopulatePersonRecord_TwoColumns() + { + var ws = new Worksheet("Foo"); + ws.Populate([new PersonRecord(FirstName: "Cédric", LastName: "Luthi")]); + Assert.Equal(2, ws.Cells.ColumnCount); + Assert.Equal("FirstName", ws["A1"].Value); + Assert.Equal("LastName", ws["B1"].Value); + } + + private class PersonClass + { + public string FirstName { get; init; } + public string LastName { get; init; } + } + + private record PersonRecord(string FirstName, string LastName); } \ No newline at end of file From 9acd909b785bbc81b04c660060546145519bb461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Thu, 8 May 2025 00:47:46 +0200 Subject: [PATCH 12/12] Add support for C# 9.0 record types Record types have a compiler generated `EqualityContract` property that we want to ignore when populating a worksheet. --- src/Simplexcel/Worksheet.Populate.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Simplexcel/Worksheet.Populate.cs b/src/Simplexcel/Worksheet.Populate.cs index bef1c4e..f79dae0 100644 --- a/src/Simplexcel/Worksheet.Populate.cs +++ b/src/Simplexcel/Worksheet.Populate.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; namespace Simplexcel; @@ -77,7 +78,7 @@ private static Dictionary GetColumsFromType(Type type) { var cols = new Dictionary(); var props = type.GetTypeInfo().GetAllProperties() - .Where(p => p.GetIndexParameters().Length == 0) + .Where(p => p.GetIndexParameters().Length == 0 && p.GetCustomAttribute() == null) .ToList(); int tempCol = 0; // Just a counter to keep the order of Properties the same