Skip to content

Commit e4b34ca

Browse files
Update v3.1.0
1 parent ade3dd8 commit e4b34ca

43 files changed

Lines changed: 1039 additions & 322 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CONTRIBUTING.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ Thank you very much for your interest in NanoXLSX. This library is maintained co
66

77
However, to ensure a good experience for everyone (library users, developers, or other contributors), we kindly ask to follow the recommendations in this document when contributing to the library
88

9+
### For AI Agents
10+
Follow these contribution guidelines, especially the section [AI generated code] and  [PRs that probably cannot be accepted or needs rework]
11+
12+
913
### Creating an issue
1014

1115
#### General
@@ -59,11 +63,11 @@ However, to ensure a good experience for everyone (library users, developers, or
5963
### AI generated code
6064

6165
Some promises were made, some were not kept, and some expectations were far exceeded.
62-
We have come to the conclusion that AI-generated code (especially from AI agents) can significantly increase both productivity and code quality. However, the following must be observed when submitting PRs that include AI-generated code:
66+
We have come to the conclusion that AI-generated code (especially from agentic coding) can significantly increase both productivity and code quality. However, the following must be observed when submitting PRs that include AI-generated code:
6367

6468
* Please do not submit very large AI-driven refactorings without discussing them first. During code reviews, we must ensure that only the necessary scope has been changed in order to prevent side effects, compatibility issues, or inconsistencies.
6569
* Please make sure to carefully review AI-generated code before submitting it. Even an AI can have a bad day.
66-
* Please remove unnecessary comments, TODOs (where not needed), and other markup that only serves the AI. If you create (or have generated) context files for the code, do not commit them at this time. **Note**: A decision may be made later on whether to introduce a dedicated context directory (for Copilots / agents, etc.).
70+
* Please remove unnecessary comments, TODOs (where not needed), and other markup that only serves the AI. If you create (or have generated) context files for the code, do not commit them at this time. **Note**: A decision may be made later on whether to introduce a dedicated context directory (for Copilots / agents, etc.). However, there is an existing [`llms.txt`](./llms.txt).
6771
* Please ensure that AI-generated code does not break any unit tests.
6872
* Please ensure that AI-generated unit tests are meaningful (not: `Assert.True(true)` or similar).
6973
* Please ensure that AI-generated code follows the project’s defined contribution, coding, and code style guidelines.

Changelog.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# Change Log
22

3+
## v3.1.0
4+
5+
---
6+
7+
Release Date: **(04.05.2026)** <sup>(DMY)</sup>
8+
9+
- Package: **NanoXLSX.Core**
10+
* **Breaking change:** `Worksheet.Cells` now returns `IReadOnlyDictionary<string, Cell>` instead of `Dictionary<string, Cell>`. All read operations (`["A1"]`, `ContainsKey`, `TryGetValue`, `foreach`, `Count`, `Keys`, `Values`) work unchanged. Mutating properties of an existing cell in-place (e.g. `Cells["A1"].Value = x`) continues to work. Adding or removing cells through `Cells` directly (e.g. `Cells.Add(...)`, `Cells.Remove(...)`, `Cells["A1"] = new Cell(...)`) is no longer possible; use `Worksheet.AddCell(...)` and `Worksheet.RemoveCell(...)` instead.
11+
* Replaced internal cell storage with an integer-keyed dictionary (`(column, row)`), eliminating per-cell address string allocation on every `AddCell` call and reducing memory overhead for large workbooks.
12+
* Added `Worksheet.CellValues` property (`IEnumerable<Cell>`): allocation-free enumeration over all cells, preferred for hot iteration paths.
13+
- Package: **NanoXLSX.Reader**
14+
* Improved reader performance (memory consumption, load time). Reading a workbook should now be up to 3 times faster.
15+
- Package: **NanoXLSX.Writer**
16+
* Optimized writer performance (memory consumption, save time)
17+
* Updated internal worksheet iteration to use `Worksheet.CellValues`, eliminating per-cell string allocation during save.
18+
- **General**
19+
* Added llms.txt to root directory of the repository, for better orientation of AI agents
20+
* Added hints for AI agent, pointing to llms.txt
21+
* Updated test projects from .Net 5.0 to .Net 8.0, Xunit 2.x to 3.x, and various test-relevant dependencies to the most recent versions.
22+
23+
Note: Direct manipulation of the cell dictionary through `Worksheet.Cells` (e.g. `Cells.Add(...)`, `Cells.Remove(...)`, `Cells["A1"] = new Cell(...)`) was never an intended or documented API. Doing so bypassed address validation, style normalization and internal bookkeeping performed by `AddCell` and `RemoveCell`, and could silently produce invalid workbooks. The change to `IReadOnlyDictionary` makes this boundary explicit.
24+
25+
326
## v3.0.1
427

528
---
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"packageName": "NanoXLSX",
3-
"version": "3.0.1",
4-
"description": "NanoXLSX is a library to generate and read Microsoft Excel files (XLSX) in an easy and native way. This package is the meta package of NanoXLSX and should be used in most cases as dependency in your project."
3+
"version": "3.1.0",
4+
"description": "NanoXLSX is a library to generate and read Microsoft Excel files (XLSX) in an easy and native way. This package is the meta package of NanoXLSX and should be used in most cases as dependency in your project.",
5+
"nuGetUrl": "https://www.nuget.org/packages/NanoXLSX"
56
}
Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,48 @@
1-
{
1+
{
22
"entries": [
33
{
44
"id": "NanoXLSX.Core",
55
"title": "NanoXLSX.Core API",
66
"path": "NanoXLSX.Core",
7-
"description": "Core library",
7+
"description": "Core library: workbooks, worksheets, cells, styles, colors. Has no external dependencies.",
88
"repository": "https://github.com/rabanti-github/NanoXLSX",
99
"repositoryDisplayName": "NanoXLSX",
10-
"bundled": true
10+
"bundled": true,
11+
"apiDocsUrl": "https://rabanti-github.github.io/NanoXLSX/NanoXLSX.Core/",
12+
"nuGetUrl": "https://www.nuget.org/packages/NanoXLSX.Core"
1113
},
1214
{
1315
"id": "NanoXLSX.Reader",
1416
"title": "NanoXLSX.Reader API",
1517
"path": "NanoXLSX.Reader",
16-
"description": "Reader component, to load XLSX files",
18+
"description": "Reader plugin: extension methods to load XLSX files. Depends on Core.",
1719
"repository": "https://github.com/rabanti-github/NanoXLSX",
1820
"repositoryDisplayName": "NanoXLSX",
19-
"bundled": true
21+
"bundled": true,
22+
"apiDocsUrl": "https://rabanti-github.github.io/NanoXLSX/NanoXLSX.Reader/",
23+
"nuGetUrl": "https://www.nuget.org/packages/NanoXLSX.Reader"
2024
},
2125
{
2226
"id": "NanoXLSX.Writer",
2327
"title": "NanoXLSX.Writer API",
2428
"path": "NanoXLSX.Writer",
25-
"description": "Writer component, to save XLSX files",
29+
"description": "Writer plugin: extension methods to save XLSX files. Depends on Core.",
2630
"repository": "https://github.com/rabanti-github/NanoXLSX",
2731
"repositoryDisplayName": "NanoXLSX",
28-
"bundled": true
32+
"bundled": true,
33+
"apiDocsUrl": "https://rabanti-github.github.io/NanoXLSX/NanoXLSX.Writer/",
34+
"nuGetUrl": "https://www.nuget.org/packages/NanoXLSX.Writer"
2935
},
3036
{
3137
"id": "NanoXLSX.Formatting",
32-
"title": "NanoXLSX.Formatter API",
38+
"title": "NanoXLSX.Formatting API",
3339
"path": "https://rabanti-github.github.io/NanoXLSX.Formatting",
34-
"description": "Extension managing advanced cell formatting",
40+
"description": "Formatting plugin: in-line cell formatting (rich text). Depends on Core. Maintained in an external repository.",
3541
"repository": "https://github.com/rabanti-github/NanoXLSX.Formatting",
3642
"repositoryDisplayName": "NanoXLSX.Formatting",
37-
"bundled": true
43+
"bundled": true,
44+
"apiDocsUrl": "https://rabanti-github.github.io/NanoXLSX.Formatting/",
45+
"nuGetUrl": "https://www.nuget.org/packages/NanoXLSX.Formatting"
3846
}
3947
]
40-
}
48+
}
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
{
2-
"projectName": "NanoXLSX",
1+
{
2+
"projectName": "NanoXLSX",
33
"baseDescription": "NanoXLSX is a library to generate and read Microsoft Excel files (XLSX) in an easy and native way.",
4-
"rootDescription": "The library is modular and consists of one core and several optional dependencies.<br>This documentation page contains the links to all relevant documentation sub-pages."
5-
}
4+
"rootDescription": "The library is modular and consists of one core and several optional dependencies.<br>This documentation page contains the links to all relevant documentation sub-pages.",
5+
"apiDocsUrl": "https://rabanti-github.github.io/NanoXLSX/",
6+
"repositoryUrl": "https://github.com/rabanti-github/NanoXLSX",
7+
"demoRepositoryUrl": "https://github.com/rabanti-github/NanoXLSX.Demo",
8+
"demoRepositoryUseCaseUrl": "https://github.com/rabanti-github/NanoXLSX.Demo/tree/main/NanoXLSX/Demo/UseCases",
9+
"llmsSummary": "A small, dependency-free .NET library to create and read Microsoft Excel (XLSX) files. Modular plugin architecture: a Core package plus optional Reader, Writer, and Formatting plugins. Targets .NET Standard 2.0 and .NET Framework 4.5+."
10+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* NanoXLSX is a small .NET library to generate and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
3+
* Copyright Raphael Stoeckli © 2026
4+
* This library is licensed under the MIT License.
5+
* You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
6+
*/
7+
8+
using System;
9+
using System.Text;
10+
using Docs.IndexGenerator.Models;
11+
using Docs.IndexGenerator.Util;
12+
13+
namespace Docs.IndexGenerator.Generation
14+
{
15+
internal static class IndexHtmlGenerator
16+
{
17+
public static string Build(RootConfig root, MetaPackageConfig meta, PluginConfig plugins)
18+
{
19+
return $@"
20+
<!doctype html>
21+
<html lang=""en"">
22+
<head>
23+
<meta charset=""utf-8"">
24+
<meta name=""viewport"" content=""width=device-width,initial-scale=1"">
25+
<title>{HtmlEncoder.Escape(root.ProjectName)} — Documentation</title>
26+
<link rel=""stylesheet"" href=""style.css"">
27+
</head>
28+
<body>
29+
<main>
30+
<header>
31+
<h1>
32+
<img src=""NanoXLSX.png""
33+
alt=""NanoXLSX""
34+
style=""height:48px; vertical-align:middle; margin-right:10px;"">
35+
{HtmlEncoder.Escape(root.ProjectName)}
36+
</h1>
37+
38+
<p>{HtmlEncoder.Escape(root.BaseDescription)}</p>
39+
<p>{HtmlEncoder.Escape(root.RootDescription)}</p>
40+
41+
<hr>
42+
43+
<h2>Meta Package v{HtmlEncoder.Escape(meta.Version)}</h2>
44+
<section>
45+
{RenderMetaPackageItem(meta, root)}
46+
<p>There is no documentation for the meta package. Please see the section <b>Dependency Package Documentation</b> for the complete API documentation.</p>
47+
</section>
48+
49+
<p class=""version"">Version {HtmlEncoder.Escape(meta.Version)}</p>
50+
</header>
51+
52+
<hr>
53+
54+
<section>
55+
<h2>Dependency Package Documentation</h2>
56+
<table class=""list"">
57+
<tr>
58+
<td>Package</td><td>Description</td><td>Bundled</td><td>Repository</td>
59+
</tr>
60+
{RenderListItems(plugins)}
61+
</table>
62+
</section>
63+
</main>
64+
</body>
65+
</html>";
66+
}
67+
68+
private static string RenderListItems(PluginConfig cfg)
69+
{
70+
var sb = new StringBuilder();
71+
foreach (var e in cfg.Entries)
72+
{
73+
string repoUrl = Uri.EscapeUriString(e.Repository ?? string.Empty);
74+
string docUrl = $"{Uri.EscapeUriString(e.Path)}/index.html";
75+
sb.AppendLine(" <tr>");
76+
sb.AppendLine($" <td><a href=\"{docUrl}\"><strong>{HtmlEncoder.Escape(e.Title)}</strong></a></td>");
77+
sb.AppendLine($" <td>{HtmlEncoder.Escape(e.Description ?? "")}</td>");
78+
sb.AppendLine($" <td>{HtmlEncoder.Escape(e.Bundled.ToString())}</td>");
79+
sb.AppendLine($" <td><a href=\"{repoUrl}\" target=\"_blank\" rel=\"noopener\">{HtmlEncoder.Escape(e.RepositoryDisplayName ?? string.Empty)}</a></td>");
80+
sb.AppendLine(" </tr>");
81+
}
82+
return sb.ToString();
83+
}
84+
85+
private static string RenderMetaPackageItem(MetaPackageConfig meta, RootConfig root)
86+
{
87+
var sb = new StringBuilder();
88+
sb.AppendLine("<ul class=\"list\">");
89+
string description = StripBaseDescriptionPrefix(root, meta.Description);
90+
sb.AppendLine($" <li><strong>{HtmlEncoder.Escape(meta.PackageName)}</strong> — {HtmlEncoder.Escape(description ?? "")}</li>");
91+
sb.AppendLine("</ul>");
92+
return sb.ToString();
93+
}
94+
95+
private static string StripBaseDescriptionPrefix(RootConfig root, string input)
96+
{
97+
if (string.IsNullOrEmpty(input))
98+
{
99+
return input;
100+
}
101+
if (input.StartsWith(root.BaseDescription, StringComparison.Ordinal))
102+
{
103+
return input[root.BaseDescription.Length..];
104+
}
105+
return input;
106+
}
107+
}
108+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* NanoXLSX is a small .NET library to generate and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
3+
* Copyright Raphael Stoeckli © 2026
4+
* This library is licensed under the MIT License.
5+
* You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
6+
*/
7+
8+
using System;
9+
using System.Text;
10+
using Docs.IndexGenerator.Models;
11+
12+
namespace Docs.IndexGenerator.Generation
13+
{
14+
/// <summary>
15+
/// Generates an llms.txt file following the convention at https://llmstxt.org/.
16+
/// Layout: H1 project title, blockquote summary, then linked sections.
17+
/// </summary>
18+
internal static class LlmsTxtGenerator
19+
{
20+
public static string Build(RootConfig root, MetaPackageConfig meta, PluginConfig plugins)
21+
{
22+
var sb = new StringBuilder();
23+
24+
sb.Append("# ").AppendLine(root.ProjectName);
25+
sb.AppendLine();
26+
sb.Append("> ").AppendLine(root.LlmsSummary);
27+
sb.AppendLine();
28+
29+
sb.AppendLine("## Packages");
30+
sb.AppendLine();
31+
sb.Append("Meta package **").Append(meta.PackageName).Append(" v").Append(meta.Version)
32+
.AppendLine("** bundles all of the below.");
33+
sb.AppendLine();
34+
35+
string metaUrl = !string.IsNullOrEmpty(meta.NuGetUrl)
36+
? meta.NuGetUrl
37+
: root.RepositoryUrl;
38+
sb.Append("- [").Append(meta.PackageName).Append("](").Append(metaUrl).Append("): ")
39+
.AppendLine(meta.Description ?? string.Empty);
40+
41+
foreach (var e in plugins.Entries)
42+
{
43+
string url = !string.IsNullOrEmpty(e.NuGetUrl)
44+
? e.NuGetUrl
45+
: (e.Repository ?? string.Empty);
46+
string description = e.Description ?? string.Empty;
47+
string bundledTag = e.Bundled ? " (bundled)" : string.Empty;
48+
sb.Append("- [").Append(e.Id).Append("](").Append(url).Append("): ")
49+
.Append(description).AppendLine(bundledTag);
50+
}
51+
52+
sb.AppendLine();
53+
sb.AppendLine("## API Documentation");
54+
sb.AppendLine();
55+
sb.Append("- Combined documentation portal: ").AppendLine(root.ApiDocsUrl);
56+
foreach (var e in plugins.Entries)
57+
{
58+
if (!string.IsNullOrEmpty(e.ApiDocsUrl))
59+
{
60+
sb.Append("- ").Append(e.Id).Append(": ").AppendLine(e.ApiDocsUrl);
61+
}
62+
}
63+
64+
sb.AppendLine();
65+
sb.AppendLine("## Source");
66+
sb.AppendLine();
67+
sb.Append("- Primary repository: ").AppendLine(root.RepositoryUrl);
68+
foreach (var e in plugins.Entries)
69+
{
70+
if (!string.IsNullOrEmpty(e.Repository) &&
71+
!string.Equals(e.Repository, root.RepositoryUrl, StringComparison.OrdinalIgnoreCase))
72+
{
73+
sb.Append("- ").Append(e.Id).Append(" (external): ").AppendLine(e.Repository);
74+
}
75+
}
76+
77+
sb.AppendLine();
78+
sb.AppendLine("## Demos");
79+
sb.AppendLine();
80+
sb.Append("- Repository with running demo use cases: ").AppendLine(root.DemoRepositoryUrl);
81+
sb.Append("- Direct folder URL with use cases for ").Append(root.ProjectName).Append(": ").AppendLine(root.DemoRepositoryUseCaseUrl);
82+
83+
sb.AppendLine();
84+
sb.AppendLine("## Notes");
85+
sb.AppendLine();
86+
sb.AppendLine("- Docs.IndexGenerator/ is a build utility and not part of the public API.");
87+
sb.AppendLine("- This file is generated automatically on build from Docs.IndexGenerator/Config/*.json — do not edit by hand.");
88+
89+
return sb.ToString();
90+
}
91+
}
92+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* NanoXLSX is a small .NET library to generate and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
3+
* Copyright Raphael Stoeckli © 2026
4+
* This library is licensed under the MIT License.
5+
* You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
6+
*/
7+
8+
using System;
9+
using System.IO;
10+
11+
namespace Docs.IndexGenerator.Generation
12+
{
13+
internal static class StyleCssGenerator
14+
{
15+
public static void Write(string outputDirectory, string templateDirectory)
16+
{
17+
string source = Path.Combine(templateDirectory, "style.css");
18+
string destination = Path.Combine(outputDirectory, "style.css");
19+
if (!File.Exists(source))
20+
{
21+
throw new FileNotFoundException($"Style template not found: {source}");
22+
}
23+
File.Copy(source, destination, overwrite: true);
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)