Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Named cell styles #65

Merged
merged 43 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7967418
API for named styles
sveinungf Jul 13, 2024
b5d7f1a
Implement GetStyleId
sveinungf Jul 26, 2024
0b894c7
Validate name when adding a named style
sveinungf Jul 26, 2024
ea4b024
Simplify comparing with default alignment
sveinungf Jul 26, 2024
4c69d2e
Remove unnecessary passing of SpreadsheetBuffer as argument in Worksh…
sveinungf Jul 26, 2024
33280c3
Enumerators for styles XML
sveinungf Jul 26, 2024
912daf5
Merge branch 'main' into dev/named-cell-styles
sveinungf Jul 26, 2024
46d50d5
Fix a potential issue with the order of styles
sveinungf Jul 27, 2024
affb3cb
Update public API snapshots
sveinungf Jul 27, 2024
fbd5da4
Writer for cellStyles in styles.xml
sveinungf Jul 27, 2024
012da35
Add TODO comments for styles.xml
sveinungf Jul 27, 2024
692466c
Move styles.xml writers to subfolder
sveinungf Jul 27, 2024
a5f29a0
Rename styles.xml part writers
sveinungf Jul 27, 2024
0d09e00
Move the styles.xml "xf" part writing to a separate class
sveinungf Jul 27, 2024
744d801
Write cellStyleXfs entries for named styles in styles.xml
sveinungf Jul 28, 2024
2ea8166
Handle hidden named styles
sveinungf Aug 4, 2024
4408a91
Add StyleNameVisibility to package validation suppression
sveinungf Aug 4, 2024
abdde8a
Validate style name
sveinungf Aug 4, 2024
5e49a22
Handle styles with a StyleManager
sveinungf Aug 9, 2024
b9c6554
Move the DefaultStyling field to StyleManager
sveinungf Aug 10, 2024
79d10f5
Handle the default DateTime format in StyleManager
sveinungf Aug 10, 2024
4ffab09
TryAddNamedStyle in StyleManager
sveinungf Aug 10, 2024
e508af2
Add Name to StyleIdentifiers
sveinungf Aug 10, 2024
fff0ee2
Update public API snapshots after renaming parameter
sveinungf Aug 10, 2024
99f759d
Write named style index (xfId) for cellXfs entries
sveinungf Aug 10, 2024
95a6f7a
Fix missing equal sign for hidden named cell styles
sveinungf Aug 10, 2024
985ea11
Tests for named styles
sveinungf Aug 10, 2024
f6e8ce1
Fix incorrect xfId for named styles
sveinungf Aug 10, 2024
99348e9
Test for using a named style in multiple cells
sveinungf Aug 10, 2024
3a80b2b
Test for multiple named styles
sveinungf Aug 10, 2024
e78fddb
Remove TODO comment after testing
sveinungf Aug 10, 2024
72a85dd
XML doc for adding named style
sveinungf Aug 10, 2024
8134b98
Test for named style with invalid visibility
sveinungf Aug 10, 2024
3b8845b
XML doc for StyleNameVisibility
sveinungf Aug 10, 2024
ea5e583
Tests for returned StyleId of duplicate styles
sveinungf Aug 11, 2024
8fa685f
Allow duplicate styles with different names
sveinungf Aug 11, 2024
aca72a3
Avoid creating another dictionary when writing named styles to styles…
sveinungf Aug 11, 2024
e5e5fa8
Fix a worksheet lookup in a test
sveinungf Aug 11, 2024
63b6751
Fix test
sveinungf Aug 11, 2024
1c03f8f
Fix missing assignment of StyleManager
sveinungf Aug 11, 2024
c466e11
Tests with null DefaultDateTimeFormat
sveinungf Aug 11, 2024
d6276ab
Tests for uncovered code
sveinungf Aug 11, 2024
a7af7ae
Fix test for double comparison by using precision
sveinungf Aug 11, 2024
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
16 changes: 16 additions & 0 deletions SpreadCheetah.Test/Tests/AlignmentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using SpreadCheetah.Styling;

namespace SpreadCheetah.Test.Tests;

public class AlignmentTests
{
[Fact]
public void Alignment_Indent_InvalidValue()
{
// Arrange
var alignment = new Alignment();

// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => alignment.Indent = -1);
}
}
269 changes: 269 additions & 0 deletions SpreadCheetah.Test/Tests/NamedStyleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
using OfficeOpenXml;
using SpreadCheetah.Styling;
using SpreadCheetah.Test.Helpers;
using System.Drawing;

namespace SpreadCheetah.Test.Tests;

public class NamedStyleTests
{
private static readonly SpreadCheetahOptions SpreadCheetahOptions = new() { BufferSize = SpreadCheetahOptions.MinimumBufferSize };

[Theory]
[InlineData(StyleNameVisibility.Visible)]
[InlineData(StyleNameVisibility.Hidden)]
public async Task Spreadsheet_AddStyle_NamedStyle(StyleNameVisibility visibility)
{
// Arrange
using var stream = new MemoryStream();
await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream, SpreadCheetahOptions);
await spreadsheet.StartWorksheetAsync("My sheet");
var style = new Style { Font = { Bold = true } };
const string name = "My bold style";

// Act
var addedStyleId = spreadsheet.AddStyle(style, name, visibility);
var returnedStyleId = spreadsheet.GetStyleId(name);
await spreadsheet.FinishAsync();

// Assert
Assert.Equal(addedStyleId, returnedStyleId);
SpreadsheetAssert.Valid(stream);
using var package = new ExcelPackage(stream);
var namedStyles = package.Workbook.Styles.NamedStyles;
var namedStyle = Assert.Single(namedStyles, x => !x.Name.Equals("Normal", StringComparison.Ordinal));
Assert.Equal(name, namedStyle.Name);
Assert.True(namedStyle.Style.Font.Bold);
}

[Fact]
public async Task Spreadsheet_AddStyle_NamedStyleWithNoVisiblity()
{
// Arrange
using var stream = new MemoryStream();
await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream, SpreadCheetahOptions);
await spreadsheet.StartWorksheetAsync("My sheet");
var style = new Style { Font = { Bold = true } };
const string name = "My bold style";

// Act
var addedStyleId = spreadsheet.AddStyle(style, name);
var returnedStyleId = spreadsheet.GetStyleId(name);
await spreadsheet.FinishAsync();

// Assert
Assert.Equal(addedStyleId, returnedStyleId);
SpreadsheetAssert.Valid(stream);
using var package = new ExcelPackage(stream);
var namedStyle = Assert.Single(package.Workbook.Styles.NamedStyles);
Assert.Equal("Normal", namedStyle.Name);
Assert.False(namedStyle.Style.Font.Bold);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task Spreadsheet_AddStyle_NamedStyleUsedByCell(bool withDefaultDateTimeFormat)
{
// Arrange
using var stream = new MemoryStream();
var options = new SpreadCheetahOptions { BufferSize = SpreadCheetahOptions.MinimumBufferSize };
if (!withDefaultDateTimeFormat)
options.DefaultDateTimeFormat = null;

await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream, options);
await spreadsheet.StartWorksheetAsync("My sheet");
var style = new Style { Font = { Bold = true } };
const string name = "My bold style";

// Act
var styleId = spreadsheet.AddStyle(style, name, StyleNameVisibility.Visible);
await spreadsheet.AddRowAsync([new StyledCell("My cell", styleId)]);
await spreadsheet.FinishAsync();

// Assert
SpreadsheetAssert.Valid(stream);
using var package = new ExcelPackage(stream);
var worksheet = Assert.Single(package.Workbook.Worksheets);
var actualCell = Assert.Single(worksheet.Cells);
Assert.Equal(name, actualCell.StyleName);
Assert.True(actualCell.Style.Font.Bold);
}

[Fact]
public async Task Spreadsheet_AddStyle_NamedStyleUsedByMultipleCells()
{
// Arrange
using var stream = new MemoryStream();
await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream, SpreadCheetahOptions);
var style = new Style { Font = { Bold = true } };
const string name = "My bold style";

// Act
var styleId = spreadsheet.AddStyle(style, name, StyleNameVisibility.Visible);
await spreadsheet.StartWorksheetAsync("Sheet 1");
await spreadsheet.AddRowAsync([new StyledCell("A1", styleId), new StyledCell(2, null), new StyledCell(3, styleId)]);
await spreadsheet.AddRowAsync([new StyledCell("A2", styleId)]);
await spreadsheet.StartWorksheetAsync("Sheet 2");
await spreadsheet.AddRowAsync([new StyledCell("A1", styleId)]);
await spreadsheet.FinishAsync();

// Assert
SpreadsheetAssert.Valid(stream);
using var package = new ExcelPackage(stream);
var worksheet1 = package.Workbook.Worksheets["Sheet 1"];
Assert.Equal(name, worksheet1.Cells["A1"].StyleName);
Assert.NotEqual(name, worksheet1.Cells["B2"].StyleName);
Assert.Equal(name, worksheet1.Cells["C1"].StyleName);
Assert.Equal(name, worksheet1.Cells["A2"].StyleName);

var worksheet2 = package.Workbook.Worksheets["Sheet 2"];
Assert.Equal(name, worksheet2.Cells["A1"].StyleName);
}

[Fact]
public async Task Spreadsheet_AddStyle_MultipleNamedStylesUsedByMultipleCells()
{
// Arrange
using var stream = new MemoryStream();
await using var spreadsheet = await Spreadsheet.CreateNewAsync(stream, SpreadCheetahOptions);
(string Name, Style Style)[] styles =
[
("Bold", new Style { Font = { Bold = true } }),
("Italic", new Style { Font = { Italic = true } }),
("Red", new Style { Fill = { Color = Color.Red } }),
("Blue", new Style { Fill = { Color = Color.Blue } })
];

// Act
var styleIds = styles.Select(x => spreadsheet.AddStyle(x.Style, x.Name, StyleNameVisibility.Visible)).ToList();

await spreadsheet.StartWorksheetAsync("Sheet 1");
await spreadsheet.AddRowAsync([new StyledCell("A1", styleIds[0]), new StyledCell(2, null), new StyledCell(3, styleIds[1])]);
await spreadsheet.AddRowAsync([new StyledCell("A2", styleIds[2])]);
await spreadsheet.StartWorksheetAsync("Sheet 2");
await spreadsheet.AddRowAsync([new StyledCell("A1", styleIds[3])]);
await spreadsheet.FinishAsync();

// Assert
SpreadsheetAssert.Valid(stream);
using var package = new ExcelPackage(stream);
var worksheet1 = package.Workbook.Worksheets["Sheet 1"];
Assert.Equal(styles[0].Name, worksheet1.Cells["A1"].StyleName);
Assert.Equal(styles[1].Name, worksheet1.Cells["C1"].StyleName);
Assert.Equal(styles[2].Name, worksheet1.Cells["A2"].StyleName);

var worksheet2 = package.Workbook.Worksheets["Sheet 2"];
Assert.Equal(styles[3].Name, worksheet2.Cells["A1"].StyleName);
}

[Fact]
public async Task Spreadsheet_AddStyle_NamedStyleWithInvalidVisibility()
{
// Arrange
await using var spreadsheet = await Spreadsheet.CreateNewAsync(Stream.Null, SpreadCheetahOptions);
var style = new Style { Font = { Bold = true } };

// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(() => spreadsheet.AddStyle(style, "Name", (StyleNameVisibility)3));
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData(" Style")]
[InlineData("Style ")]
[InlineData("Normal")]
public async Task Spreadsheet_AddStyle_NamedStyleWithInvalidName(string? name)
{
// Arrange
await using var spreadsheet = await Spreadsheet.CreateNewAsync(Stream.Null, SpreadCheetahOptions);
var style = new Style { Font = { Bold = true } };

// Act & Assert
Assert.ThrowsAny<ArgumentException>(() => spreadsheet.AddStyle(style, name!));
}

[Fact]
public async Task Spreadsheet_AddStyle_NamedStyleWithTooLongName()
{
// Arrange
await using var spreadsheet = await Spreadsheet.CreateNewAsync(Stream.Null, SpreadCheetahOptions);
var style = new Style { Font = { Bold = true } };
var name = new string('c', 256);

// Act & Assert
Assert.Throws<ArgumentException>(() => spreadsheet.AddStyle(style, name));
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Spreadsheet_AddStyle_NamedStyleWithDuplicateName(bool differentCasing)
{
// Arrange
await using var spreadsheet = await Spreadsheet.CreateNewAsync(Stream.Null, SpreadCheetahOptions);

var style = new Style { Font = { Bold = true } };
const string name = "My bold style";
spreadsheet.AddStyle(style, name);

var otherStyle = new Style { Font = { Italic = true } };
var duplicateName = differentCasing ? name.ToUpperInvariant() : name;

// Act & Assert
Assert.Throws<ArgumentException>(() => spreadsheet.AddStyle(otherStyle, duplicateName));
}

[Fact]
public async Task Spreadsheet_AddStyle_DuplicateNamedStylesReturnsDifferentStyleIds()
{
// Arrange
await using var spreadsheet = await Spreadsheet.CreateNewAsync(Stream.Null, SpreadCheetahOptions);
var style1 = new Style { Font = { Bold = true } };
var style2 = style1 with { };

// Act
var styleId1 = spreadsheet.AddStyle(style1, "Style 1");
var styleId2 = spreadsheet.AddStyle(style2, "Style 2");

// Assert
Assert.NotEqual(styleId1.Id, styleId2.Id);
}

[Fact]
public async Task Spreadsheet_AddStyle_NamedStyleDuplicateOfUnnamedStyleReturnsDifferentStyleIds()
{
// Arrange
await using var spreadsheet = await Spreadsheet.CreateNewAsync(Stream.Null, SpreadCheetahOptions);
var style1 = new Style { Font = { Bold = true } };
var style2 = style1 with { };

// Act
var styleId1 = spreadsheet.AddStyle(style1);
var styleId2 = spreadsheet.AddStyle(style2, "Style 2");

// Assert
Assert.NotEqual(styleId1.Id, styleId2.Id);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Spreadsheet_GetStyleId_IncorrectName(bool existingNamedStyle)
{
// Arrange
await using var spreadsheet = await Spreadsheet.CreateNewAsync(Stream.Null, SpreadCheetahOptions);

if (existingNamedStyle)
{
var style = new Style { Font = { Bold = true } };
const string name = "My bold style";
spreadsheet.AddStyle(style, name);
}

// Act & Assert
Assert.Throws<SpreadCheetahException>(() => spreadsheet.GetStyleId("Other style"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ namespace SpreadCheetah
public System.Threading.Tasks.ValueTask AddRowAsync(System.ReadOnlyMemory<SpreadCheetah.DataCell> cells, SpreadCheetah.Worksheets.RowOptions? options, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask AddRowAsync(System.ReadOnlyMemory<SpreadCheetah.StyledCell> cells, SpreadCheetah.Worksheets.RowOptions? options, System.Threading.CancellationToken token = default) { }
public SpreadCheetah.Styling.StyleId AddStyle(SpreadCheetah.Styling.Style style) { }
public SpreadCheetah.Styling.StyleId AddStyle(SpreadCheetah.Styling.Style style, string name, SpreadCheetah.Styling.StyleNameVisibility? nameVisibility = default) { }
public void Dispose() { }
public System.Threading.Tasks.ValueTask DisposeAsync() { }
public System.Threading.Tasks.ValueTask<SpreadCheetah.Images.EmbeddedImage> EmbedImageAsync(System.IO.Stream stream, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask FinishAsync(System.Threading.CancellationToken token = default) { }
public SpreadCheetah.Styling.StyleId GetStyleId(string name) { }
public void MergeCells(string cellRange) { }
public System.Threading.Tasks.ValueTask StartWorksheetAsync(string name, SpreadCheetah.Worksheets.WorksheetOptions? options = null, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask StartWorksheetAsync<T>(string name, SpreadCheetah.SourceGeneration.WorksheetRowTypeInfo<T> typeInfo, System.Threading.CancellationToken token = default) { }
Expand Down Expand Up @@ -400,6 +402,11 @@ namespace SpreadCheetah.Styling
{
public int Id { get; }
}
public enum StyleNameVisibility
{
Visible = 0,
Hidden = 1,
}
public enum VerticalAlignment
{
Bottom = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ namespace SpreadCheetah
public System.Threading.Tasks.ValueTask AddRowAsync(System.ReadOnlyMemory<SpreadCheetah.DataCell> cells, SpreadCheetah.Worksheets.RowOptions? options, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask AddRowAsync(System.ReadOnlyMemory<SpreadCheetah.StyledCell> cells, SpreadCheetah.Worksheets.RowOptions? options, System.Threading.CancellationToken token = default) { }
public SpreadCheetah.Styling.StyleId AddStyle(SpreadCheetah.Styling.Style style) { }
public SpreadCheetah.Styling.StyleId AddStyle(SpreadCheetah.Styling.Style style, string name, SpreadCheetah.Styling.StyleNameVisibility? nameVisibility = default) { }
public void Dispose() { }
public System.Threading.Tasks.ValueTask DisposeAsync() { }
public System.Threading.Tasks.ValueTask<SpreadCheetah.Images.EmbeddedImage> EmbedImageAsync(System.IO.Stream stream, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask FinishAsync(System.Threading.CancellationToken token = default) { }
public SpreadCheetah.Styling.StyleId GetStyleId(string name) { }
public void MergeCells(string cellRange) { }
public System.Threading.Tasks.ValueTask StartWorksheetAsync(string name, SpreadCheetah.Worksheets.WorksheetOptions? options = null, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask StartWorksheetAsync<T>(string name, SpreadCheetah.SourceGeneration.WorksheetRowTypeInfo<T> typeInfo, System.Threading.CancellationToken token = default) { }
Expand Down Expand Up @@ -400,6 +402,11 @@ namespace SpreadCheetah.Styling
{
public int Id { get; }
}
public enum StyleNameVisibility
{
Visible = 0,
Hidden = 1,
}
public enum VerticalAlignment
{
Bottom = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ namespace SpreadCheetah
public System.Threading.Tasks.ValueTask AddRowAsync(System.ReadOnlyMemory<SpreadCheetah.DataCell> cells, SpreadCheetah.Worksheets.RowOptions? options, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask AddRowAsync(System.ReadOnlyMemory<SpreadCheetah.StyledCell> cells, SpreadCheetah.Worksheets.RowOptions? options, System.Threading.CancellationToken token = default) { }
public SpreadCheetah.Styling.StyleId AddStyle(SpreadCheetah.Styling.Style style) { }
public SpreadCheetah.Styling.StyleId AddStyle(SpreadCheetah.Styling.Style style, string name, SpreadCheetah.Styling.StyleNameVisibility? nameVisibility = default) { }
public void Dispose() { }
public System.Threading.Tasks.ValueTask DisposeAsync() { }
public System.Threading.Tasks.ValueTask<SpreadCheetah.Images.EmbeddedImage> EmbedImageAsync(System.IO.Stream stream, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask FinishAsync(System.Threading.CancellationToken token = default) { }
public SpreadCheetah.Styling.StyleId GetStyleId(string name) { }
public void MergeCells(string cellRange) { }
public System.Threading.Tasks.ValueTask StartWorksheetAsync(string name, SpreadCheetah.Worksheets.WorksheetOptions? options = null, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask StartWorksheetAsync<T>(string name, SpreadCheetah.SourceGeneration.WorksheetRowTypeInfo<T> typeInfo, System.Threading.CancellationToken token = default) { }
Expand Down Expand Up @@ -400,6 +402,11 @@ namespace SpreadCheetah.Styling
{
public int Id { get; }
}
public enum StyleNameVisibility
{
Visible = 0,
Hidden = 1,
}
public enum VerticalAlignment
{
Bottom = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,12 @@ namespace SpreadCheetah
public System.Threading.Tasks.ValueTask AddRowAsync(System.ReadOnlyMemory<SpreadCheetah.DataCell> cells, SpreadCheetah.Worksheets.RowOptions? options, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask AddRowAsync(System.ReadOnlyMemory<SpreadCheetah.StyledCell> cells, SpreadCheetah.Worksheets.RowOptions? options, System.Threading.CancellationToken token = default) { }
public SpreadCheetah.Styling.StyleId AddStyle(SpreadCheetah.Styling.Style style) { }
public SpreadCheetah.Styling.StyleId AddStyle(SpreadCheetah.Styling.Style style, string name, SpreadCheetah.Styling.StyleNameVisibility? nameVisibility = default) { }
public void Dispose() { }
public System.Threading.Tasks.ValueTask DisposeAsync() { }
public System.Threading.Tasks.ValueTask<SpreadCheetah.Images.EmbeddedImage> EmbedImageAsync(System.IO.Stream stream, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask FinishAsync(System.Threading.CancellationToken token = default) { }
public SpreadCheetah.Styling.StyleId GetStyleId(string name) { }
public void MergeCells(string cellRange) { }
public System.Threading.Tasks.ValueTask StartWorksheetAsync(string name, SpreadCheetah.Worksheets.WorksheetOptions? options = null, System.Threading.CancellationToken token = default) { }
public System.Threading.Tasks.ValueTask StartWorksheetAsync<T>(string name, SpreadCheetah.SourceGeneration.WorksheetRowTypeInfo<T> typeInfo, System.Threading.CancellationToken token = default) { }
Expand Down Expand Up @@ -399,6 +401,11 @@ namespace SpreadCheetah.Styling
{
public int Id { get; }
}
public enum StyleNameVisibility
{
Visible = 0,
Hidden = 1,
}
public enum VerticalAlignment
{
Bottom = 0,
Expand Down
Loading
Loading