Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Excel_PRIME.RangeBench/Excel_PRIME.RangeBench.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspose.Cells" Version="25.12.0" />
<PackageReference Include="Aspose.Cells" Version="26.1.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="ClosedXML" Version="0.105.0" />
<PackageReference Include="EPPlus-LGPL" Version="4.5.3.13" />
Expand Down
2 changes: 1 addition & 1 deletion Excel_PRIME.Tests/Excel_PRIME.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="6.0.0">
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="System.Linq.Async" Version="7.0.0" />
Expand Down
2 changes: 1 addition & 1 deletion Excel_PRIME.Tests/SheetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task A010_StepThroughEmptyXlsx(string fileName, int expected)
}

[Test]
[TestCase("Data/multisheet1.xlsx", new[] { "one", "two", "three", "b", "a" })]
[TestCase("Data/multisheet1.xlsx", new[] { "one", "two", "three", "b", "a", "a (2)", "a (3)", "b (2)", "one (3)", "two (3)", "three (3)", "b (3)" })]
[TestCase("Data/singlesheet.xlsx", new[] { "one" })]
[TestCase("Data/sample_file_bad.xlsx", new[] { "MasterInvoice_Detailed_XLSX" })] // This file contains package relations that use absolute rooted paths instead of relative paths. "/xl/workbook.xml" vs "xl/workbook.xml"
[TestCase("Data/sample_file_good.xlsx", new[] { "MasterInvoice_Detailed_XLSX" })]
Expand Down
2 changes: 1 addition & 1 deletion Excel_PRIME/Excel_PRIME.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@


<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.102" PrivateAssets="All" />
</ItemGroup>

<PropertyGroup>
Expand Down
2 changes: 0 additions & 2 deletions Excel_PRIME/Implementation/XmlReaderHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ public Task<IOpenXmlSheetReaderAsync> CreateSheetReaderAsync(NonClosingStream st
return Task.FromResult(reader);
}

public string GetSheetFileName(int offsetSheetId) => $"xl/worksheets/sheet{offsetSheetId}.xml";

/// <InheritDoc />
public IOpenXmlSheetReader CreateSheetReader(NonClosingStream stream, InstanceContext instanceContext,
XmlNameTable sharedNameTable, CancellationToken ct)
Expand Down
8 changes: 4 additions & 4 deletions Excel_PRIME/Implementation/XmlWorkBookReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
private protected XmlReader _readerWb;
private bool _isDisposed;

protected XmlWorkBookReader(IZipReader zipReader) => _zipReader = zipReader;

Check warning on line 21 in Excel_PRIME/Implementation/XmlWorkBookReader.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_readerWb' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 21 in Excel_PRIME/Implementation/XmlWorkBookReader.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_streamWb' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

public XmlWorkBookReader(IZipReader zipReader, CancellationToken _)
: this(zipReader)
Expand All @@ -44,7 +44,7 @@

public IEnumerable<KeyValuePair<string, string>> GetSheetNames([EnumeratorCancellation] CancellationToken ct)
{
IReadOnlyDictionary<string, string> worksheetRels = PopulateWorkSheetRels(ct);
Dictionary<string, string> worksheetRels = PopulateWorkSheetRels(ct);

string sheetsRefAtom = _readerWb.NameTable.Add("sheets");
if (!worksheetRels.Any()
Expand Down Expand Up @@ -78,10 +78,10 @@
}
}

private IReadOnlyDictionary<string, string> PopulateWorkSheetRels(CancellationToken ct)
private Dictionary<string, string> PopulateWorkSheetRels(CancellationToken ct)
{
using Stream? streamRelWb = _zipReader.GetEntry("xl/_rels/workbook.xml.rels");
using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings

Check warning on line 84 in Excel_PRIME/Implementation/XmlWorkBookReader.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'input' in 'XmlReader XmlReader.Create(Stream input, XmlReaderSettings? settings)'.
{
DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources
IgnoreComments = true, // Skip parsing and allocating strings for comments
Expand Down Expand Up @@ -251,7 +251,7 @@

public async IAsyncEnumerable<KeyValuePair<string, string>> GetSheetNamesAsync([EnumeratorCancellation] CancellationToken ct)
{
IReadOnlyDictionary<string, string> worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false);
Dictionary<string, string> worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false);

string sheetsRefAtom = _readerWb.NameTable.Add("sheets");
if (!worksheetRels.Any()
Expand Down Expand Up @@ -284,10 +284,10 @@
}
}

private async Task<IReadOnlyDictionary<string, string>> PopulateWorkSheetRelsAsync(CancellationToken ct)
private async Task<Dictionary<string, string>> PopulateWorkSheetRelsAsync(CancellationToken ct)
{
using Stream? streamRelWb = await _zipReaderA.GetEntryAsync("xl/_rels/workbook.xml.rels", ct).ConfigureAwait(false);
using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings

Check warning on line 290 in Excel_PRIME/Implementation/XmlWorkBookReader.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'input' in 'XmlReader XmlReader.Create(Stream input, XmlReaderSettings? settings)'.
{
DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources
IgnoreComments = true, // Skip parsing and allocating strings for comments
Expand Down
2 changes: 0 additions & 2 deletions Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ public Task<IOpenXmlSheetReaderAsync> CreateSheetReaderAsync(NonClosingStream st
return Task.FromResult(reader);
}

public string GetSheetFileName(int offsetSheetId) => $"xl/worksheets/sheet{offsetSheetId}.bin";

/// <InheritDoc />
public IOpenXmlSheetReader CreateSheetReader(NonClosingStream stream, InstanceContext instanceContext,
XmlNameTable _, CancellationToken ct)
Expand Down
8 changes: 4 additions & 4 deletions Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
private protected XlsbStreamReader _readerWb;
private bool _isDisposed;

protected XlsbWorkBookReader(IZipReader zipReader) => _zipReader = zipReader;

Check warning on line 24 in Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_readerWb' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 24 in Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field '_streamWb' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

public XlsbWorkBookReader(IZipReader zipReader, CancellationToken _)
: this(zipReader)
Expand All @@ -37,7 +37,7 @@

public IEnumerable<KeyValuePair<string, string>> GetSheetNames([EnumeratorCancellation] CancellationToken ct)
{
IReadOnlyDictionary<string, string> worksheetRels = PopulateWorkSheetRels(ct);
Dictionary<string, string> worksheetRels = PopulateWorkSheetRels(ct);
if (!worksheetRels.Any())
{
yield break;
Expand Down Expand Up @@ -75,10 +75,10 @@
nextRecord.Dispose();
}

private IReadOnlyDictionary<string, string> PopulateWorkSheetRels(CancellationToken ct)
private Dictionary<string, string> PopulateWorkSheetRels(CancellationToken ct)
{
using Stream? streamRelWb = _zipReader.GetEntry("xl/_rels/workbook.bin.rels");
using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings

Check warning on line 81 in Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'input' in 'XmlReader XmlReader.Create(Stream input, XmlReaderSettings? settings)'.
{
DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources
IgnoreComments = true, // Skip parsing and allocating strings for comments
Expand Down Expand Up @@ -305,7 +305,7 @@

public async IAsyncEnumerable<KeyValuePair<string, string>> GetSheetNamesAsync([EnumeratorCancellation] CancellationToken ct)
{
IReadOnlyDictionary<string, string> worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false);
Dictionary<string, string> worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false);
if (!worksheetRels.Any())
{
yield break;
Expand Down Expand Up @@ -343,10 +343,10 @@
nextRecord.Dispose();
}

private async Task<IReadOnlyDictionary<string, string>> PopulateWorkSheetRelsAsync(CancellationToken ct)
private async Task<Dictionary<string, string>> PopulateWorkSheetRelsAsync(CancellationToken ct)
{
using Stream? streamRelWb = await _zipReaderA.GetEntryAsync("xl/_rels/workbook.bin.rels", ct).ConfigureAwait(false);
using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings

Check warning on line 349 in Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'input' in 'XmlReader XmlReader.Create(Stream input, XmlReaderSettings? settings)'.
{
DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources
IgnoreComments = true, // Skip parsing and allocating strings for comments
Expand Down
2 changes: 1 addition & 1 deletion Excel_PRIMEXlsb.Bench/Excel_PRIMEXlsb.Bench.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspose.Cells" Version="25.12.0" />
<PackageReference Include="Aspose.Cells" Version="26.1.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="ExcelDataReader" Version="3.8.0" />
<PackageReference Include="Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers" Version="18.3.36812.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspose.Cells" Version="25.12.0" />
<PackageReference Include="Aspose.Cells" Version="26.1.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="ClosedXML" Version="0.105.0" />
<PackageReference Include="EPPlus-LGPL" Version="4.5.3.13" />
Expand Down
32 changes: 31 additions & 1 deletion Performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ And then slightly different versions of the following dependent on date:
```
-----

# 2026-01-16
# 2026-01-16 - V3
- Remove some `AggressiveOptimization` and allow `i-cache` to do its job
- Implement "Hot-Paths" for cell type access
- Reduce some memory allocations for ReadOnly CellCollections
Expand Down Expand Up @@ -903,3 +903,33 @@ And then slightly different versions of the following dependent on date:
| ParallelEveryCellAsyncExcel_PrimeTwice | sampl(...).xlsx [30] | 2.10x slower | 303000.0000 | 1000.0000 | - | 2420.25 MB | 8.23x more |
```

# 2026-01-18 - V4 Alpha
- Internal implementation of `IOpenXmlWorkBookReader::GetSheetNames` now returns the relative path to the sheetName
```
| Method | FileName | Ratio | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|------------------------ |--------------------- |-------------:|------------:|-----------:|-----------:|-----------:|------------:|
| Sylvan | 100mb.xlsx | baseline | 43000.0000 | 41000.0000 | 5000.0000 | 338.72 MB | |
| XlsxHelper | 100mb.xlsx | 4.24x slower | 424000.0000 | 5000.0000 | 2000.0000 | 3380.59 MB | 9.98x more |
| AsyncExcel_Prime | 100mb.xlsx | 1.39x slower | 259000.0000 | 43000.0000 | 6000.0000 | 2029.28 MB | 5.99x more |
| Excel_Prime | 100mb.xlsx | 1.19x slower | 162000.0000 | 47000.0000 | 6000.0000 | 1256.38 MB | 3.71x more |
| PrlAsyncExcel_PrimeTwice| 100mb.xlsx | 2.27x slower | 440000.0000 | 50000.0000 | 6000.0000 | 3464.3 MB | 10.23x more |
| | | | | | | | |
| Sylvan | Blank(...).xlsx [30] | baseline | 40000.0000 | 2000.0000 | 1000.0000 | 318.9 MB | |
| XlsxHelper | Blank(...).xlsx [30] | 1.01x slower | 218000.0000 | 1000.0000 | - | 1739.24 MB | 5.45x more |
| AsyncExcel_Prime | Blank(...).xlsx [30] | 1.08x slower | 347000.0000 | 42000.0000 | 41000.0000 | 2566.97 MB | 8.05x more |
| Excel_Prime | Blank(...).xlsx [30] | 1.11x faster | 195000.0000 | 42000.0000 | 41000.0000 | 1361.65 MB | 4.27x more |
| PrlAsyncExcel_PrimeTwice| Blank(...).xlsx [30] | 2.40x slower | 693000.0000 | 85000.0000 | 83000.0000 | 5155.95 MB | 16.17x more |
| | | | | | | | |
| Sylvan | sampl(...).xlsx [34] | baseline | 33000.0000 | - | - | 265.67 MB | |
| XlsxHelper | sampl(...).xlsx [34] | 1.12x faster | 100000.0000 | - | - | 799.73 MB | 3.01x more |
| AsyncExcel_Prime | sampl(...).xlsx [34] | 1.04x faster | 148000.0000 | 1000.0000 | - | 1184.05 MB | 4.46x more |
| Excel_Prime | sampl(...).xlsx [34] | 1.25x faster | 79000.0000 | 1000.0000 | - | 632.38 MB | 2.38x more |
| PrlAsyncExcel_PrimeTwice| sampl(...).xlsx [34] | 2.05x slower | 297000.0000 | 1000.0000 | - | 2376.06 MB | 8.94x more |
| | | | | | | | |
| Sylvan | sampl(...).xlsx [30] | baseline | 36000.0000 | - | - | 294.16 MB | |
| XlsxHelper | sampl(...).xlsx [30] | 1.09x faster | 93000.0000 | - | - | 742.13 MB | 2.52x more |
| AsyncExcel_Prime | sampl(...).xlsx [30] | 1.03x slower | 151000.0000 | 1000.0000 | - | 1207.31 MB | 4.10x more |
| Excel_Prime | sampl(...).xlsx [30] | 1.22x faster | 82000.0000 | 1000.0000 | - | 656.03 MB | 2.23x more |
| PrlAsyncExcel_PrimeTwice| sampl(...).xlsx [30] | 2.17x slower | 303000.0000 | - | - | 2420.4 MB | 8.23x more |
```
-----
73 changes: 29 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@ Lets take each of the above elements and explain:

<a href="https://info.flagcounter.com/dxXK"><img src="https://s01.flagcounter.com/map/dxXK/size_l/txt_000000/border_CCCCCC/pageviews_1/viewers_0/flags_0/" alt="Flag Counter" border="0"></a>
-----
<!--TOC-->
- [Targets 🎯](#targets-)
- [Phase 0](#phase-0)
- [Phase Alpha](#phase-alpha)
- [Phase Beta - Benchmarks ⏱️](#phase-beta-benchmarks-)
- [Phase 1 - MVP 🔍](#phase-1-mvp-)
- [Phase 2 - RC](#phase-2-rc)
- [V2 Changes](#v2-changes)
- [2025-12-14](#2025-12-14)
- [Phase V3 - XLS**B** 💾 (BIFF12)](#phase-v3-xlsb-biff12)
- [V3 Changes](#v3-changes)
- [2026-01-16](#2026-01-16)
- [Phase V4 - Specific Cell value type(s) #️⃣ - Alpha](#phase-v4-specific-cell-value-types-alpha)
- [Phase 5 - Third Party Nugets 📦](#phase-5-third-party-nugets-)
- [Phase 6 - ideas 💡](#phase-6-ideas-)
<!--/TOC-->
-----

# Targets 🎯
## Phase 0
Expand Down Expand Up @@ -137,92 +154,52 @@ Lets take each of the above elements and explain:

## Phase 1 - MVP 🔍
- ✅ Add `IEnumerable`s and benchmark
- ⚠️ Still not convinced whether to implement "all the way down"
- ✅ Implement `XmlReader.Create` for
- ✅ Loading sharedStrings
- ✅ Sheet loading
- ✅ Some Profiling Enahancements
- ✅ Performance [2025-10-18-pm](Performance.md#2025-10-18-pm)
- ✅ More Benchmarks
- Now With `FastExcel`
- ✅ Some Profiling Enahancements
- 🚀 Big Performance improvements [2025-10-19-pm](Performance.md#2025-10-19-pm)
- ✅ Better `Storage` of the SharedStrings
- ✅ Use of LazyLoading Class
- ⚠️ Performance [2025-10-14](Performance.md#2025-10-14)
- ✅ Use of Derived `XmlNamedTable` implementations
- ✅ Locking for separate sheet thread reading
- ⚠️ Performance [2025-10-25](Performance.md##2025-10-25)
- ✅ Restricted storage (i.e. do not return things that are not relevant)
- 🚀 Big Performance improvements [2025-10-26](Performance.md#2025-10-26)
- ✅ Cell object type 📅
- 🚀 Big Performance improvements [2025-11-01](Performance.md#2025-11-01)
- ✅ Cell converted when read (i.e. you will know the type that you want, and you can convert it.)
- 🚀 Big Performance improvements [2025-11-04](Performance.md#2025-11-04)
- ✅ Use internal `ZipEntry` rented buffer
- ✅ Add and explain usage in options
- 🚀 Big Performance improvements [2025-11-07](Performance.md#2025-11-07)
- ✅ Investigation into the smallest function 💪
- 🚀 More Performance improvements [2025-11-08](Performance.md#2025-11-08)
- ✅ Optimise for `CellConversion.None` 💪
- 🚀 More Performance improvements [2025-11-12](Performance.md#2025-11-12)
- ✅ Parallel Sheet threads Access
- ✅ Multiple times (with locking)
- ✅ Nuget
- ✅ Beta etc.
- 🎊 Released as Nuget V1.yyMM.dd -> **`1.2511.14`**
-----

## Phase 2 - RC
- ✅ Add `IEnumerable`s _All_ the way down ⤵️
- i.e. remove the need for Asynchronous awaits
- 🚀 Yielding More Performance improvements [2025-11-19](Performance.md#2025-11-19)
- ⛓️‍💥 **Breaking Change** 🔩
- The Async classes now have `Async` appended to be distinct from the non async versions
- But, `Async` inherit from the non, so they are interchangable
- ✅ Nuget
- ✅ Manual workflow deploy Release
- ✅ Manual workflow deploy Beta
- ✅ Read `definedName`s (Ranges / Cell / Value / Dynamic) 📇
- ✅ Read from global
- ✅ Handle Dynamics (i.e. do not fall over! 🤷)
- ✅ Deal with blank rows in a sheet 🗋
- ✅ Return a `null` cell row
- ✅ Deal with Empty cells in a row 🗅
- ✅ Return a `null` cell (e.g. `<c r="F12" s="8"/>`)
- ✅ Implement Sheet scoping of `definedName`s
- ✅ i.e. `<definedName name="OrderSize" localSheetId="0">'Try it Yourself'!$C$12:$E$12</definedName>`
- Note: The above will be referenced as `OrderSize (Try it Yourself)` as shown in LibreOffice.
- ✅ Implement Row extraction 📟
- ✅ Allow ColumnHeader addressing (i.e. start -> end columns)
- ✅ Implement RangeExtraction 📲
- ✅ Global rangeNames
- ✅ Make `DefinedName`'s work with `localSheetId`definitions
- ✅ User defined, using the `"A1:B10"` or `"$A$1:$B$10"` syntax
- [Range Performance on 2025-12-10](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-10)
- ✅ Add Benchmarks for "Excel readers" That perform Range Extraction
- ✅ `ClosedXML` Version="0.105.0"
- [Performance on 2025-11-25](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-25)
- ✅ `EPPlus_LPGL` Version="4.5.3.13"
- [Performance on 2025-11-25](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-25)
- ⚠️ `FastExcel` Version="3.0.13" -> [Fails on Range Extraction](https://github.com/ahmedwalid05/FastExcel/issues/89)
- ✅ `FreeSpire.XLS` Version="14.2.0"
- [Performance on 2025-11-27](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-27)
- ✅ `Aspose.Cells` Version="25.11.0"
- [Performance on 2025-11-28](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-28)
- ⚠️ Extend bencmarks to cover the other large file types
- It appears that most of the others do not like the `pivot-tables` file.!! 🤯
- [Performance on 2025-11-28](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-28)
- ✅ Investigate _memory usage_(s) 🧑‍💻
- ✅ Some performance improvements 🏃‍➡️ [Performance on 2025-12-01](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-01)
- ✅ More performance improvements 🏃‍➡️ [Performance on 2025-12-04](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-04)
- ✅ Sacrificed a little speed ➡️ [Performance on 2025-12-07](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-07)
- ✅ Release as Nuget V2.2512-10 💨
-----
## V2 Changes
## V2 Changes ➡️
### 2025-12-14
- Implement [GetUserRange(...)](https://github.com/Smurf-IV/Excel_PRIME/issues/7)
- [Range Performance on 2025-12-14](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-14)

-----

## Phase V3 - XLS**B** 💾 (BIFF12)
- ⛓️‍💥 **Breaking Change(s)**
- `FileType` has been removed, and Open via the Public class type
Expand Down Expand Up @@ -260,6 +237,14 @@ Lets take each of the above elements and explain:
- 🚀 Big Performance improvements [2026-01-11](Performance.md#2026-01-11)
- 🎊 Released **V3** as Nuget `V3.2601.11`
-----
# V3 Changes ➡️
# 2026-01-16
- Remove some `AggressiveOptimization` and allow `i-cache` to do its job
- Implement "Hot-Paths" for cell type access
- Reduce some memory allocations for ReadOnly CellCollections
- [2026-01-16](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2026-01-16)

-----

## Phase V4 - Specific Cell value type(s) #️⃣ - Alpha
- ⛓️‍💥 **Breaking Change(s)**
Expand Down
Loading
Loading