Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README_V2.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ The exporters also fully support asynchronous operations:
await exporter.ExportAsync(outputPath, values);
```

#### Editing cell styles

Cell style updates are queued and applied in worksheet and cell order when `Save` is called. If the same cell is updated more than once, the last update wins.

```csharp
MiniExcel.Editors.GetOpenXmlEditor(path)
Comment thread
michelebastione marked this conversation as resolved.
.UpdateCellStyle("A1", style => style.FontColor = Color.Red)
.UpdateCellStyle("X100", style => style.FontColor = Color.Blue)
Comment thread
michelebastione marked this conversation as resolved.
.Save();
```

### Release Notes

If you're migrating from a `1.x` version, please check the [upgrade notes](V2-Upgrade-Notes.md).
Expand Down
Empty file added src/MiniExcel.Core/MiniExcel.cs
Empty file.
5 changes: 5 additions & 0 deletions src/MiniExcel.Core/MiniExcelProviders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ public sealed class MiniExcelTemplaterProvider
{
internal MiniExcelTemplaterProvider() { }
}

public sealed class MiniExcelEditorProvider
{
internal MiniExcelEditorProvider() { }
}
2 changes: 2 additions & 0 deletions src/MiniExcel.Core/MiniExcelV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public static class MiniExcelV2
public static readonly MiniExcelExporterProvider Exporters = new();
public static readonly MiniExcelImporterProvider Importers = new();
public static readonly MiniExcelTemplaterProvider Templaters = new();
public static readonly MiniExcelEditorProvider Editors = new();
}

[Obsolete("This class will be removed in the full release, use MiniExcelV2 instead.", true)]
Expand All @@ -16,4 +17,5 @@ public static class MiniExcel
public static readonly MiniExcelExporterProvider Exporters = new();
public static readonly MiniExcelImporterProvider Importers = new();
public static readonly MiniExcelTemplaterProvider Templaters = new();
public static readonly MiniExcelEditorProvider Editors = new();
}
486 changes: 486 additions & 0 deletions src/MiniExcel.OpenXml/Api/OpenXmlEditor.cs

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/MiniExcel.OpenXml/Api/ProviderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@ public static class ProviderExtensions
public static OpenXmlExporter GetOpenXmlExporter(this MiniExcelExporterProvider exporterProvider) => new();
public static OpenXmlImporter GetOpenXmlImporter(this MiniExcelImporterProvider importerProvider) => new();
public static OpenXmlTemplater GetOpenXmlTemplater(this MiniExcelTemplaterProvider templaterProvider) => new();

/// <summary>Creates an editor for an existing OpenXml workbook.</summary>
public static OpenXmlEditor GetOpenXmlEditor(this MiniExcelEditorProvider editorProvider, string path) => new(path);

/// <summary>Creates an editor for an existing OpenXml workbook stream.</summary>
public static OpenXmlEditor GetOpenXmlEditor(this MiniExcelEditorProvider editorProvider, Stream stream) => new(stream);
}
9 changes: 9 additions & 0 deletions src/MiniExcel.OpenXml/Styles/OpenXmlCellStyle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Drawing;

namespace MiniExcelLib.OpenXml.Styles;

public sealed class OpenXmlCellStyle
{
/// <summary>Gets or sets the cell font color.</summary>
public Color? FontColor { get; set; }
}
115 changes: 115 additions & 0 deletions tests/MiniExcel.OpenXml.Tests/Styles/OpenXmlEditorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System.Drawing;
using ClosedXML.Excel;
using MiniExcelLib.Tests.Common.Utils;

namespace MiniExcelLib.OpenXml.Tests.Styles;

public class OpenXmlEditorTests
{
[Fact]
public void SaveAppliesUpdatesInCellOrderAndPreservesExistingStyle()
{
using var path = AutoDeletingPath.Create();
using (var workbook = new XLWorkbook())
{
var worksheet = workbook.AddWorksheet("Data");
var firstCell = worksheet.Cell("A1");
firstCell.Value = 12.34;
firstCell.Style.NumberFormat.Format = "0.00";
firstCell.Style.Fill.BackgroundColor = XLColor.Yellow;
firstCell.Style.Border.LeftBorder = XLBorderStyleValues.Thin;
firstCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
worksheet.Cell("X100").Value = "last";
workbook.SaveAs(path.ToString());
}

MiniExcelV2.Editors.GetOpenXmlEditor(path.ToString())
.UpdateCellStyle("X100", style => style.FontColor = Color.Blue, "Data")
.UpdateCellStyle("A1", style => style.FontColor = Color.Red, "Data")
.Save();

using var updatedWorkbook = new XLWorkbook(path.ToString());
var updatedWorksheet = updatedWorkbook.Worksheet("Data");
var firstCellStyle = updatedWorksheet.Cell("A1").Style;

Assert.Equal(Color.Red.ToArgb(), firstCellStyle.Font.FontColor.Color.ToArgb());
Assert.Equal(Color.Blue.ToArgb(), updatedWorksheet.Cell("X100").Style.Font.FontColor.Color.ToArgb());
Assert.Equal("0.00", firstCellStyle.NumberFormat.Format);
Assert.Equal(XLColor.Yellow, firstCellStyle.Fill.BackgroundColor);
Assert.Equal(XLBorderStyleValues.Thin, firstCellStyle.Border.LeftBorder);
Assert.Equal(XLAlignmentHorizontalValues.Center, firstCellStyle.Alignment.Horizontal);
}

[Fact]
public void LastUpdateWinsForTheSameCell()
{
using var path = AutoDeletingPath.Create();
CreateWorkbook(path.ToString());

MiniExcelV2.Editors.GetOpenXmlEditor(path.ToString())
.UpdateCellStyle("A1", style => style.FontColor = Color.Red)
.UpdateCellStyle("A1", style => style.FontColor = Color.Blue)
.Save();

using var workbook = new XLWorkbook(path.ToString());
Assert.Equal(Color.Blue.ToArgb(), workbook.Worksheet(1).Cell("A1").Style.Font.FontColor.Color.ToArgb());
}

[Fact]
public async Task SaveAsyncUpdatesASelectedWorksheetInAStream()
{
using var stream = new MemoryStream();
using (var workbook = new XLWorkbook())
{
workbook.AddWorksheet("First").Cell("A1").Value = "first";
workbook.AddWorksheet("Second").Cell("A1").Value = "second";
workbook.SaveAs(stream);
}

await MiniExcelV2.Editors.GetOpenXmlEditor(stream)
.UpdateCellStyle("A1", style => style.FontColor = Color.Blue, "Second")
.SaveAsync();

stream.Position = 0;
using var updatedWorkbook = new XLWorkbook(stream);
Assert.NotEqual(Color.Blue.ToArgb(), updatedWorkbook.Worksheet("First").Cell("A1").Style.Font.FontColor.Color.ToArgb());
Assert.Equal(Color.Blue.ToArgb(), updatedWorkbook.Worksheet("Second").Cell("A1").Style.Font.FontColor.Color.ToArgb());
}

[Fact]
public void UpdateCellStyleRejectsInvalidReferences()
{
using var stream = new MemoryStream();
var editor = MiniExcelV2.Editors.GetOpenXmlEditor(stream);

Assert.Throws<ArgumentException>(() =>
editor.UpdateCellStyle("1A", style => style.FontColor = Color.Red));
Assert.Throws<ArgumentException>(() =>
editor.UpdateCellStyle("XFE1", style => style.FontColor = Color.Red));
Assert.Throws<ArgumentException>(() =>
editor.UpdateCellStyle("A1048577", style => style.FontColor = Color.Red));
}

[Fact]
public void FailedSaveLeavesTheOriginalWorkbookUnchanged()
{
using var path = AutoDeletingPath.Create();
CreateWorkbook(path.ToString());

var editor = MiniExcelV2.Editors.GetOpenXmlEditor(path.ToString())
.UpdateCellStyle("A1", style => style.FontColor = Color.Red)
.UpdateCellStyle("A2", style => style.FontColor = Color.Blue);

Assert.Throws<InvalidDataException>(() => editor.Save());

using var workbook = new XLWorkbook(path.ToString());
Assert.NotEqual(Color.Red.ToArgb(), workbook.Worksheet(1).Cell("A1").Style.Font.FontColor.Color.ToArgb());
}

private static void CreateWorkbook(string path)
{
using var workbook = new XLWorkbook();
workbook.AddWorksheet("Data").Cell("A1").Value = "value";
workbook.SaveAs(path);
}
}
Loading