diff --git a/.github/workflows/Publish_Beta.yml b/.github/workflows/Publish_Beta.yml index 0dbd366..c1cd3e1 100644 --- a/.github/workflows/Publish_Beta.yml +++ b/.github/workflows/Publish_Beta.yml @@ -51,7 +51,7 @@ jobs: # run: dotnet test --no-build --configuration Release --verbosity normal - name: Pack - run: dotnet pack --no-build --configuration Release --version-suffix XLSB-RC1 + run: dotnet pack --no-build --configuration Release --version-suffix Alpha - name: PushGithub run: dotnet nuget push **/*.nupkg --no-symbols --skip-duplicate --api-key ${{ secrets.GITHUB_TOKEN }} diff --git a/Excel_PRIME.RangeBench/GetRangeAsposeCells.cs b/Excel_PRIME.RangeBench/GetRangeAsposeCells.cs index 3e0c2e7..25033e1 100644 --- a/Excel_PRIME.RangeBench/GetRangeAsposeCells.cs +++ b/Excel_PRIME.RangeBench/GetRangeAsposeCells.cs @@ -23,19 +23,15 @@ public bool LoadFile(string fullPath) return true; } - public IEnumerable> GetDefinedRange(string definedName, int? localSheetId = null) + public IEnumerable> GetDefinedRange(string definedName, string? sheetName = null) { Aspose.Cells.Range namedRange; - if (localSheetId.HasValue) - { + + namedRange = !string.IsNullOrEmpty(sheetName) //Get a specific named range in the worksheet - namedRange = _workbook.Worksheets.GetRangeByName(definedName, localSheetId.Value, false); - } - else - { + ? null! //_workbook.Worksheets.GetRangeByName(definedName, sheetName, false) //Get a specific named range in the workbook - namedRange = _workbook.Worksheets.GetRangeByName(definedName); - } + : _workbook.Worksheets.GetRangeByName(definedName); if (namedRange != null) { //Get range diff --git a/Excel_PRIME.RangeBench/GetRangeClosedXML.cs b/Excel_PRIME.RangeBench/GetRangeClosedXML.cs index ded305a..e96be7f 100644 --- a/Excel_PRIME.RangeBench/GetRangeClosedXML.cs +++ b/Excel_PRIME.RangeBench/GetRangeClosedXML.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ClosedXML.Excel; @@ -22,13 +23,13 @@ public bool LoadFile(string fullPath) return wb != null; } - public IEnumerable> GetDefinedRange(string definedName, int? localSheetId = null) + public IEnumerable> GetDefinedRange(string definedName, string? sheetName = null) { IXLRanges rangesLocal; // worksheet scope - if (localSheetId.HasValue) + if (sheetName != null) { - IXLWorksheet worksheet = wb!.Worksheets.ElementAt(localSheetId.Value); + wb!.Worksheets.TryGetWorksheet(sheetName, out IXLWorksheet? worksheet); rangesLocal = worksheet.Ranges(definedName); } else diff --git a/Excel_PRIME.RangeBench/GetRangeEPPlus.cs b/Excel_PRIME.RangeBench/GetRangeEPPlus.cs index d901118..6eb49fe 100644 --- a/Excel_PRIME.RangeBench/GetRangeEPPlus.cs +++ b/Excel_PRIME.RangeBench/GetRangeEPPlus.cs @@ -23,13 +23,13 @@ public bool LoadFile(string fullPath) return excelPackage != null; } - public IEnumerable> GetDefinedRange(string definedName, int? localSheetId = null) + public IEnumerable> GetDefinedRange(string definedName, string? sheetName = null) { ExcelNamedRange? namedRange; // worksheet scope - if (localSheetId.HasValue) + if (sheetName != null) { - ExcelWorksheet? worksheet = excelPackage!.Workbook.Worksheets.ElementAt(localSheetId.Value); + ExcelWorksheet? worksheet = excelPackage!.Workbook.Worksheets[sheetName]; namedRange = worksheet.Names[definedName]; } else diff --git a/Excel_PRIME.RangeBench/GetRangeExcelPrime.cs b/Excel_PRIME.RangeBench/GetRangeExcelPrime.cs index b13f725..636c9f1 100644 --- a/Excel_PRIME.RangeBench/GetRangeExcelPrime.cs +++ b/Excel_PRIME.RangeBench/GetRangeExcelPrime.cs @@ -16,11 +16,9 @@ public bool LoadFile(string fullPath) } - public IEnumerable> GetDefinedRange(string definedName, int? localSheetId = null) + public IEnumerable> GetDefinedRange(string definedName, string? sheetName = null) { - IEnumerable rangeRows = localSheetId.HasValue - ? _workbook.GetDefinedRange(definedName, localSheetId.Value) - : _workbook.GetDefinedRange(definedName); + IEnumerable rangeRows = _workbook.GetDefinedRange(definedName, sheetName); foreach (CellValue?[] rangeRow in rangeRows) { yield return rangeRow.Select(cell => cell?.ToString()); diff --git a/Excel_PRIME.RangeBench/GetRangeFreeSpire.cs b/Excel_PRIME.RangeBench/GetRangeFreeSpire.cs index f968bdb..5203c92 100644 --- a/Excel_PRIME.RangeBench/GetRangeFreeSpire.cs +++ b/Excel_PRIME.RangeBench/GetRangeFreeSpire.cs @@ -18,13 +18,13 @@ public bool LoadFile(string fullPath) return true; } - public IEnumerable> GetDefinedRange(string definedName, int? localSheetId = null) + public IEnumerable> GetDefinedRange(string definedName, string? sheetName = null) { INamedRange namedRange; - if ( localSheetId.HasValue) + if (sheetName != null) { //Get a specific named range in the worksheet - Worksheet sheet = book.Worksheets[localSheetId.Value]; + Worksheet sheet = book.Worksheets[sheetName]; namedRange = sheet.Names.GetByName(definedName); } else diff --git a/Excel_PRIME.RangeBench/IGetRange.cs b/Excel_PRIME.RangeBench/IGetRange.cs index 8946ef1..42b9aa7 100644 --- a/Excel_PRIME.RangeBench/IGetRange.cs +++ b/Excel_PRIME.RangeBench/IGetRange.cs @@ -7,7 +7,7 @@ public interface IGetRange : IDisposable { bool LoadFile(string fullPath); - IEnumerable> GetDefinedRange(string definedName, int? localSheetId = null); + IEnumerable> GetDefinedRange(string definedName, string? sheetName = null); IEnumerable> GetRange(string userRange, string sheetName); } diff --git a/Excel_PRIME.RangeBench/RangeBenchmarks.cs b/Excel_PRIME.RangeBench/RangeBenchmarks.cs index 6c2e3b2..a739602 100644 --- a/Excel_PRIME.RangeBench/RangeBenchmarks.cs +++ b/Excel_PRIME.RangeBench/RangeBenchmarks.cs @@ -34,15 +34,15 @@ public int Access100mb(Type ranger) //< definedName name = "DışVeri_1" localSheetId = "0" hidden = "1" > 'Worksheet (2)'!$A$1:$H$27001 //< definedName name = "DışVeri_1" localSheetId = "1" hidden = "1" > 'Worksheet (3)'!$A$1:$H$4001 //< definedName name = "DışVeri_2" localSheetId = "3" hidden = "1" > Worksheet!$A$952351:$H$985351 - IEnumerable> rangeTablo3 = getRanger.GetDefinedRange("DışVeri_1", 2); + IEnumerable> rangeTablo3 = getRanger.GetDefinedRange("DışVeri_1"); int cells = rangeTablo3.Sum(row => row.Count()); - IEnumerable> rangeWorksheet = getRanger.GetDefinedRange("DışVeri_1", 3); + IEnumerable> rangeWorksheet = getRanger.GetDefinedRange("DışVeri_1"); cells += rangeWorksheet.Sum(row => row.Count()); - IEnumerable> rangeWorksheet2 = getRanger.GetDefinedRange("DışVeri_1", 0); + IEnumerable> rangeWorksheet2 = getRanger.GetDefinedRange("DışVeri_1"); cells += rangeWorksheet2.Sum(row => row.Count()); - IEnumerable> rangeWorksheet3 = getRanger.GetDefinedRange("DışVeri_1", 1); + IEnumerable> rangeWorksheet3 = getRanger.GetDefinedRange("DışVeri_1"); cells += rangeWorksheet3.Sum(row => row.Count()); - IEnumerable> rangeDışVeri_2 = getRanger.GetDefinedRange("DışVeri_2", 3); + IEnumerable> rangeDışVeri_2 = getRanger.GetDefinedRange("DışVeri_2"); cells += rangeDışVeri_2.Sum(row => row.Count()); return cells; @@ -62,7 +62,7 @@ public int AccessPivotTable(Type ranger) getRanger.LoadFile("Data\\pivot-tables.xlsx"); // - IEnumerable> filterDatabaseSheet = getRanger.GetDefinedRange("_xlnm._FilterDatabase", 2); + IEnumerable> filterDatabaseSheet = getRanger.GetDefinedRange("_xlnm._FilterDatabase"); int cells = filterDatabaseSheet.Sum(row => row.Count()); IEnumerable> filterDatabase = getRanger.GetDefinedRange("_xlnm._FilterDatabase"); cells += filterDatabase.Sum(row => row.Count()); diff --git a/Excel_PRIME.Tests/Data/multisheet1.xlsx b/Excel_PRIME.Tests/Data/multisheet1.xlsx index 03bdd99..f479f6e 100644 --- a/Excel_PRIME.Tests/Data/multisheet1.xlsx +++ b/Excel_PRIME.Tests/Data/multisheet1.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b64dcbc2b5589a82782baf78b364febcb0e50891d69dbb0d617198da0084bc5b -size 8702 +oid sha256:3a019831c43762f4c58318d113217d307aa5d73a6f873d3443e6fbf6501f953a +size 17482 diff --git a/Excel_PRIME.Tests/DefinedRangeTests.cs b/Excel_PRIME.Tests/DefinedRangeTests.cs index 49b2997..23d0c72 100644 --- a/Excel_PRIME.Tests/DefinedRangeTests.cs +++ b/Excel_PRIME.Tests/DefinedRangeTests.cs @@ -136,7 +136,7 @@ public void A052_ExcelPrime_PivotTable() getRanger.LoadFile("Data\\pivot-tables.xlsx"); // - IEnumerable> filterDatabaseSheet = getRanger.GetDefinedRange("_xlnm._FilterDatabase", 2); + IEnumerable> filterDatabaseSheet = getRanger.GetDefinedRange("_xlnm._FilterDatabase"); int cells = filterDatabaseSheet.Sum(row => row.Count()); IEnumerable> filterDatabase = getRanger.GetDefinedRange("_xlnm._FilterDatabase"); cells += filterDatabase.Sum(row => row.Count()); diff --git a/Excel_PRIME.Tests/DefinedRangeTestsXlsb.cs b/Excel_PRIME.Tests/DefinedRangeTestsXlsb.cs index 5b836ba..c73d427 100644 --- a/Excel_PRIME.Tests/DefinedRangeTestsXlsb.cs +++ b/Excel_PRIME.Tests/DefinedRangeTestsXlsb.cs @@ -130,7 +130,7 @@ public void A052_ExcelPrime_PivotTable() // // In the Xlsb, the `_xlnm.` portion of the name is dropped // - IEnumerable> filterDatabaseSheet = getRanger.GetDefinedRange("_FilterDatabase", 2); + IEnumerable> filterDatabaseSheet = getRanger.GetDefinedRange("_FilterDatabase"); int cells = filterDatabaseSheet.Sum(row => row.Count()); IEnumerable> filterDatabase = getRanger.GetDefinedRange("_FilterDatabase"); cells += filterDatabase.Sum(row => row.Count()); diff --git a/Excel_PRIME/DefinedRange.cs b/Excel_PRIME/DefinedRange.cs index dc0daa9..992b189 100644 --- a/Excel_PRIME/DefinedRange.cs +++ b/Excel_PRIME/DefinedRange.cs @@ -76,7 +76,6 @@ public DefinedRange(in string sheetName, ReadOnlySpan columnStart, ReadOnl { SheetName = sheetName; Name = string.Empty; - SheetIdReference = string.Empty; ExcelColumnStart = columnStart.GetColNumber(); ExcelColumnEnd = columnEnd.GetColNumber(); ExcelRowStart = rowStart; @@ -101,7 +100,6 @@ public DefinedRange(in string userRange, in string sheetName) SheetName = sheetName; Name = userRange; - SheetIdReference = string.Empty; if (userRange.Contains(':')) { DoExtractBasedOnCellRange(userRange); @@ -253,10 +251,10 @@ private void DoExtractBasedOnCellRange(ReadOnlySpan range) // e.g. "$C$12: /// public int ExcelRowEnd { get; private set; } - /// - /// Xml.Value; - /// - public string SheetIdReference { get; init; } + ///// + ///// Xml.Value; + ///// + //public string SheetIdReference { get; init; } /// /// Xml.Value; diff --git a/Excel_PRIME/Excel_PRIME.cs b/Excel_PRIME/Excel_PRIME.cs index b403d3d..e3c73bc 100644 --- a/Excel_PRIME/Excel_PRIME.cs +++ b/Excel_PRIME/Excel_PRIME.cs @@ -25,8 +25,8 @@ public class Excel_PRIME : IExcel_PRIMEAsync private readonly IOpenXmlReaderHelpersAsync _xmlReaderHelper; private readonly IZipReaderAsync _zipReader; private Stream? _fs; - private readonly Dictionary _sheetFiles = []; - private IReadOnlyDictionary _sheetNamesToOffsetSheetId = new Dictionary(); + private readonly Dictionary _sheetFiles = []; + private IReadOnlyDictionary _sheetNamesToPathOffset = new Dictionary().AsReadOnly(); private readonly InstanceContext _instanceContext = new(); private readonly SemaphoreLocker _locker = new(); private IReadOnlyDictionary? _definedRanges; @@ -117,17 +117,17 @@ private async Task GetSheetNamesAsync(IZipReaderAsync zipReader, CancellationTok { using IOpenXmlWorkBookReaderAsync wbr = await _xmlReaderHelper.CreateWorkBookReaderAsync(zipReader, ct) .ConfigureAwait(false); - _sheetNamesToOffsetSheetId = wbr.GetSheetNamesAsync(ct).ToBlockingEnumerable(ct).ToDictionary(); + _sheetNamesToPathOffset = wbr.GetSheetNamesAsync(ct).ToBlockingEnumerable(ct).ToDictionary(); } private void GetSheetNames(IZipReader zipReader, CancellationToken ct) { using IOpenXmlWorkBookReader wbr = _xmlReaderHelper.CreateWorkBookReader(zipReader, ct); - _sheetNamesToOffsetSheetId = wbr.GetSheetNames(ct).ToDictionary(); + _sheetNamesToPathOffset = wbr.GetSheetNames(ct).ToDictionary(); } /// - public IEnumerable SheetNames() => _sheetNamesToOffsetSheetId.Keys; + public IEnumerable SheetNames() => _sheetNamesToPathOffset.Keys; /// public virtual async IAsyncEnumerable GetDefinedRangeAsync(string rangeName, string? useThisSheetName = null, [EnumeratorCancellation] CancellationToken ct = default) @@ -137,7 +137,7 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) // Lazy load on first use using IOpenXmlWorkBookReaderAsync wbr = await _xmlReaderHelper.CreateWorkBookReaderAsync(_zipReader, ct) .ConfigureAwait(false); - _definedRanges = await wbr.GetDefinedRangesAsync(_sheetNamesToOffsetSheetId, ct).ConfigureAwait(false); + _definedRanges = await wbr.GetDefinedRangesAsync(_sheetNamesToPathOffset, ct).ConfigureAwait(false); } if (!_definedRanges.TryGetValue(rangeName, out DefinedRange? definedRange)) @@ -151,9 +151,9 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) yield break; } - string definedRangeSheetName = useThisSheetName ?? definedRange.SheetName ?? - _sheetNamesToOffsetSheetId.FirstOrDefault(kvp => kvp.Value == definedRange.SheetIdReference.IntParse()).Key; - if (!_sheetNamesToOffsetSheetId.ContainsKey(definedRangeSheetName)) + string? definedRangeSheetName = useThisSheetName ?? definedRange.SheetName; + if (string.IsNullOrEmpty(definedRangeSheetName) + || !_sheetNamesToPathOffset.ContainsKey(definedRangeSheetName)) { // range might be the following definition // OFFSET(Sheet1!$A$1,0,0,COUNTA(Sheet1!$A:$A),1) @@ -173,19 +173,6 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) } } - /// - public virtual IEnumerable GetDefinedRange(string rangeName, int useLocalSheetId, [EnumeratorCancellation] CancellationToken ct = default) - { - string? useThisSheetName = null; - int valueOffset = useLocalSheetId + 1; - KeyValuePair firstOrDefault = _sheetNamesToOffsetSheetId.FirstOrDefault(kvp => kvp.Value == valueOffset); - if (!string.IsNullOrEmpty(firstOrDefault.Key)) - { - useThisSheetName = firstOrDefault.Key; - } - return GetDefinedRange(rangeName, useThisSheetName, ct); - } - /// public IEnumerable GetDefinedRange(string rangeName, string? useThisSheetName = null, [EnumeratorCancellation] CancellationToken ct = default) { @@ -193,7 +180,7 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) { // Lazy load on first use using IOpenXmlWorkBookReader wbr = _xmlReaderHelper.CreateWorkBookReader(_zipReader, ct); - _definedRanges = wbr.GetDefinedRanges(_sheetNamesToOffsetSheetId, ct); + _definedRanges = wbr.GetDefinedRanges(_sheetNamesToPathOffset, ct); } DefinedRange? definedRange = null; @@ -218,10 +205,16 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) yield break; } - using ISheet? targetSheet = GetSheet( - useThisSheetName ?? definedRange.SheetName ?? - _sheetNamesToOffsetSheetId.First(kvp => kvp.Value == definedRange.SheetIdReference.IntParse()).Key, - false, ct); + string? definedRangeSheetName = useThisSheetName ?? definedRange.SheetName; + if (string.IsNullOrEmpty(definedRangeSheetName)) + { + // range might be the following definition + // OFFSET(Sheet1!$A$1,0,0,COUNTA(Sheet1!$A:$A),1) + // Or user has made a mistake + yield break; + } + + using ISheet? targetSheet = GetSheet( definedRangeSheetName, false, ct); if (targetSheet == null) { yield break; @@ -269,7 +262,7 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) /// public async Task GetSheetAsync(string sheetName, TernaryBool overrideOptionsAndUseSheetOnlyOnce = null, CancellationToken ct = default) { - if (!_sheetNamesToOffsetSheetId.TryGetValue(sheetName, out int offsetSheetId)) + if (!_sheetNamesToPathOffset.TryGetValue(sheetName, out string pathOffsetSheet)) { throw new KeyNotFoundException($"{sheetName} does not exist"); } @@ -281,13 +274,12 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) { TempFile sheetFile = await _locker.LockAsync(async () => { - if (!_sheetFiles.TryGetValue(offsetSheetId, out TempFile? sheetFile)) + if (!_sheetFiles.TryGetValue(pathOffsetSheet, out TempFile? sheetFile)) { - sheetFile = new TempFile($"sheet{offsetSheetId}"); - _sheetFiles[offsetSheetId] = sheetFile; + sheetFile = new TempFile(Path.GetFileName(pathOffsetSheet)); + _sheetFiles[pathOffsetSheet] = sheetFile; using FileStream targetStream = sheetFile.OpenForAsyncWrite(); - string sheetFileName = _xmlReaderHelper.GetSheetFileName(offsetSheetId); - await _zipReader.CopyToAsync(sheetFileName, targetStream, ct).ConfigureAwait(false); + await _zipReader.CopyToAsync(pathOffsetSheet, targetStream, ct).ConfigureAwait(false); } return sheetFile; @@ -296,17 +288,16 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) } else { - string sheetFileName = _xmlReaderHelper.GetSheetFileName(offsetSheetId); - stream = _zipReader.GetEntry(sheetFileName)!; + stream = _zipReader.GetEntry(pathOffsetSheet)!; } - return new Sheet(stream, _xmlReaderHelper, sheetName, offsetSheetId, _instanceContext); + return new Sheet(stream, _xmlReaderHelper, sheetName, _instanceContext); } /// public ISheet? GetSheet(string sheetName, TernaryBool overrideOptionsAndUseSheetOnlyOnce = null, CancellationToken ct = default) { // Find Id - if (!_sheetNamesToOffsetSheetId.TryGetValue(sheetName, out int offsetSheetId)) + if (!_sheetNamesToPathOffset.TryGetValue(sheetName, out string pathOffsetSheet)) { throw new KeyNotFoundException($"{sheetName} does not exist"); } @@ -319,23 +310,21 @@ private void GetSheetNames(IZipReader zipReader, CancellationToken ct) TempFile? sheetFile = null; _locker.Lock(() => { - if (!_sheetFiles.TryGetValue(offsetSheetId, out sheetFile)) + if (!_sheetFiles.TryGetValue(pathOffsetSheet, out sheetFile)) { - sheetFile = new TempFile($"sheet{offsetSheetId}"); - _sheetFiles[offsetSheetId] = sheetFile; + sheetFile = new TempFile(Path.GetFileName(pathOffsetSheet)); + _sheetFiles[pathOffsetSheet] = sheetFile; using FileStream targetStream = sheetFile.OpenForAsyncWrite(); - string sheetFileName = _xmlReaderHelper.GetSheetFileName(offsetSheetId); - _zipReader.CopyTo(sheetFileName, targetStream, ct); + _zipReader.CopyTo(pathOffsetSheet, targetStream, ct); } }); stream = sheetFile!.OpenForAsyncRead(true); } else { - string sheetFileName = _xmlReaderHelper.GetSheetFileName(offsetSheetId); - stream = _zipReader.GetEntry(sheetFileName)!; + stream = _zipReader.GetEntry(pathOffsetSheet)!; } - return new Sheet(stream, _xmlReaderHelper, sheetName, offsetSheetId, _instanceContext); + return new Sheet(stream, _xmlReaderHelper, sheetName, _instanceContext); } /// @@ -354,7 +343,7 @@ protected virtual void Dispose(bool isDisposing) { _instanceContext.SharedStrings?.Dispose(); _instanceContext.SharedStrings = null; - foreach ((int _, TempFile tf) in _sheetFiles) + foreach ((string _, TempFile tf) in _sheetFiles) { tf.Dispose(); } diff --git a/Excel_PRIME/Excel_PRIME.csproj b/Excel_PRIME/Excel_PRIME.csproj index a869f2b..4f882a7 100644 --- a/Excel_PRIME/Excel_PRIME.csproj +++ b/Excel_PRIME/Excel_PRIME.csproj @@ -41,7 +41,7 @@ E.xcel P.erformant R.eader via I.nterfaces for M.emory E.fficiency. Without using any external libraries. Optimised for Range extraction Smurf-IV False - 3.$([System.DateTime]::UtcNow.ToString(yyMM)).$([System.DateTime]::UtcNow.ToString(dd)) + 4.$([System.DateTime]::UtcNow.ToString(yyMM)).$([System.DateTime]::UtcNow.ToString(dd)) $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/../Release_Notes.md")) true diff --git a/Excel_PRIME/FromExternal/NonClosingStreamWrapper.cs b/Excel_PRIME/FromExternal/NonClosingStreamWrapper.cs index 17a418e..82f5d77 100644 --- a/Excel_PRIME/FromExternal/NonClosingStreamWrapper.cs +++ b/Excel_PRIME/FromExternal/NonClosingStreamWrapper.cs @@ -107,10 +107,7 @@ protected override void Dispose(bool disposing) } /// If you want to allow explicit close of the inner stream: - public void CloseInnerStream() - { - _inner.Close(); - } + public void CloseInnerStream() => _inner.Close(); /// public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => diff --git a/Excel_PRIME/IExcelImp.cs b/Excel_PRIME/IExcelImp.cs index 5493bda..5eae64d 100644 --- a/Excel_PRIME/IExcelImp.cs +++ b/Excel_PRIME/IExcelImp.cs @@ -39,16 +39,6 @@ public interface IExcelImp : IDisposable IEnumerable GetDefinedRange(string rangeName, string? useThisSheetName = null, [EnumeratorCancellation] CancellationToken ct = default); - /// - /// From the `definedName`s in the xlsx, use the name to return the range data - /// - /// - /// If passed in, then check that the range exists in that first, before switching to the global name - /// - /// - IEnumerable GetDefinedRange(string rangeName, int useLocalSheetId, - [EnumeratorCancellation] CancellationToken ct = default); - /// /// User defined range (With or Without `$`'s, e.g., `A1:B2`) /// diff --git a/Excel_PRIME/ISheet.cs b/Excel_PRIME/ISheet.cs index 03f5c3b..a91b6bb 100644 --- a/Excel_PRIME/ISheet.cs +++ b/Excel_PRIME/ISheet.cs @@ -16,11 +16,6 @@ public interface ISheet : IDisposable /// string Name { get; } - /// - /// Excel Index of this worksheet (Starts at 1) - /// - int Index { get; } - /// /// What are the Max dimension defined [Excel Rows, Excel Cells] (Many may be blank) /// @@ -98,5 +93,4 @@ public interface ISheetAsync : ISheet /// Using $A$1:$A$1 style, to return data from: a single cell, a single column, a matrix / table /// IAsyncEnumerable GetDefinedRangeAsync(DefinedRange range, [EnumeratorCancellation] CancellationToken ct = default); - } \ No newline at end of file diff --git a/Excel_PRIME/IXmlReaderHelpers.cs b/Excel_PRIME/IXmlReaderHelpers.cs index 4e20742..d2f1efc 100644 --- a/Excel_PRIME/IXmlReaderHelpers.cs +++ b/Excel_PRIME/IXmlReaderHelpers.cs @@ -70,9 +70,4 @@ Task GetSharedStringsAsync(IZipReaderAsync zipReader, bool option /// Task CreateSheetReaderAsync(NonClosingStream stream, InstanceContext instanceContext, XmlNameTable sharedNameTable, CancellationToken ct); - - /// - /// Get the internal file name of this worksheet type - /// - string GetSheetFileName(int offsetSheetId); } \ No newline at end of file diff --git a/Excel_PRIME/IXmlReaders.cs b/Excel_PRIME/IXmlReaders.cs index 0a322b8..564f59f 100644 --- a/Excel_PRIME/IXmlReaders.cs +++ b/Excel_PRIME/IXmlReaders.cs @@ -13,12 +13,13 @@ public interface IOpenXmlWorkBookReader : IDisposable /// /// What it says on the tin /// - IEnumerable> GetSheetNames(CancellationToken ct); + IEnumerable> GetSheetNames(CancellationToken ct); /// /// What it says on the tin /// - IReadOnlyDictionary GetDefinedRanges(IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct); + IReadOnlyDictionary GetDefinedRanges( + IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct); } /// @@ -29,13 +30,13 @@ public interface IOpenXmlWorkBookReaderAsync : IOpenXmlWorkBookReader /// /// What it says on the tin /// - IAsyncEnumerable> GetSheetNamesAsync(CancellationToken ct); + IAsyncEnumerable> GetSheetNamesAsync(CancellationToken ct); /// /// What it says on the tin /// Task> GetDefinedRangesAsync( - IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct); + IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct); } /// diff --git a/Excel_PRIME/Implementation/RestrictedNameTable.cs b/Excel_PRIME/Implementation/RestrictedNameTable.cs index d9f8886..fba18ed 100644 --- a/Excel_PRIME/Implementation/RestrictedNameTable.cs +++ b/Excel_PRIME/Implementation/RestrictedNameTable.cs @@ -166,7 +166,7 @@ internal sealed class SheetRestrictedNameTable : NameTable internal sealed class WorkBookRestrictedNameTable : NameTable { - private const string idAtom = "id"; + private const string idAtom = "r:id"; private const string nameAtom = "name"; private const string sheetAtom = "sheet"; private const string sheetsAtom = "sheets"; @@ -196,9 +196,12 @@ internal sealed class WorkBookRestrictedNameTable : NameTable { return idAtom; } - break; case 4: + if (value.SequenceEqual(idAtom)) + { + return idAtom; + } if (value.SequenceEqual(nameAtom)) { return nameAtom; diff --git a/Excel_PRIME/Implementation/Sheet.cs b/Excel_PRIME/Implementation/Sheet.cs index d7ecd3c..178bec4 100644 --- a/Excel_PRIME/Implementation/Sheet.cs +++ b/Excel_PRIME/Implementation/Sheet.cs @@ -19,22 +19,18 @@ internal sealed class Sheet : ISheetAsync private readonly NonClosingStream _stream; private IOpenXmlSheetReaderAsync? _sheetReader; - internal Sheet(Stream stream, IOpenXmlReaderHelpersAsync xmlReaderHelper, string name, int index, InstanceContext instanceContext) + internal Sheet(Stream stream, IOpenXmlReaderHelpersAsync xmlReaderHelper, string name, InstanceContext instanceContext) { _stream = new NonClosingStream(stream); _xmlReaderHelper = xmlReaderHelper; _instanceContext = instanceContext; _sharedNameTable = new SheetRestrictedNameTable(); Name = name; - Index = index; } /// public string Name { get; } - /// - public int Index { get; } - /// public (int Height, int Width) SheetDimensions { diff --git a/Excel_PRIME/Implementation/XmlReaderHelpers.cs b/Excel_PRIME/Implementation/XmlReaderHelpers.cs index 79b03f2..32df16d 100644 --- a/Excel_PRIME/Implementation/XmlReaderHelpers.cs +++ b/Excel_PRIME/Implementation/XmlReaderHelpers.cs @@ -75,18 +75,14 @@ public ISharedString GetSharedStrings(IZipReader zipReader, bool optionsAccessEx /// - public async Task CreateWorkBookReaderAsync(IZipReaderAsync zipReader, CancellationToken ct) + public Task CreateWorkBookReaderAsync(IZipReaderAsync zipReader, CancellationToken ct) { - Stream? stream = await zipReader.GetEntryAsync("xl/workbook.xml", ct).ConfigureAwait(false); - return new XmlWorkBookReader(stream!, ct); + IOpenXmlWorkBookReaderAsync xmlWorkBookReaderAsync = new XmlWorkBookReaderAsync(zipReader, ct); + return Task.FromResult(xmlWorkBookReaderAsync); } /// - public IOpenXmlWorkBookReader CreateWorkBookReader(IZipReader zipReader, CancellationToken ct) - { - Stream? stream = zipReader.GetEntry("xl/workbook.xml"); - return new XmlWorkBookReader(stream!, ct); - } + public IOpenXmlWorkBookReader CreateWorkBookReader(IZipReader zipReader, CancellationToken ct) => new XmlWorkBookReader(zipReader, ct); /// diff --git a/Excel_PRIME/Implementation/XmlWorkBookReader.cs b/Excel_PRIME/Implementation/XmlWorkBookReader.cs index 90b36d2..b105829 100644 --- a/Excel_PRIME/Implementation/XmlWorkBookReader.cs +++ b/Excel_PRIME/Implementation/XmlWorkBookReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -10,16 +11,24 @@ namespace ExcelPRIME.Implementation; -internal sealed class XmlWorkBookReader : IOpenXmlWorkBookReaderAsync +internal class XmlWorkBookReader : IOpenXmlWorkBookReader { - private readonly Stream _stream; - private readonly XmlReader _reader; + private protected IZipReader _zipReader; + private protected Stream _streamWb; + private protected XmlReader _readerWb; private bool _isDisposed; - public XmlWorkBookReader(Stream? stream, CancellationToken _) + protected XmlWorkBookReader(IZipReader zipReader) => _zipReader = zipReader; + + public XmlWorkBookReader(IZipReader zipReader, CancellationToken _) + : this(zipReader) { - _stream = stream!; - _reader = XmlReader.Create(_stream, new XmlReaderSettings + _streamWb = _zipReader.GetEntry("xl/workbook.xml")!; + OpenWorkbookStream(); + } + + protected void OpenWorkbookStream() => + _readerWb = XmlReader.Create(_streamWb, new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources IgnoreComments = true, // Skip parsing and allocating strings for comments @@ -32,105 +41,143 @@ public XmlWorkBookReader(Stream? stream, CancellationToken _) ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, Async = true // TBD }); - } - public async IAsyncEnumerable> GetSheetNamesAsync([EnumeratorCancellation] CancellationToken ct) + public IEnumerable> GetSheetNames([EnumeratorCancellation] CancellationToken ct) { - string sheetsRefAtom = _reader.NameTable.Add("sheets"); - if (!_reader.ReadToFollowing(sheetsRefAtom)) + IReadOnlyDictionary worksheetRels = PopulateWorkSheetRels(ct); + + string sheetsRefAtom = _readerWb.NameTable.Add("sheets"); + if (!worksheetRels.Any() + || !_readerWb.ReadToFollowing(sheetsRefAtom)) { yield break; } - string nameRefAtom = _reader.NameTable.Add("name"); - string sheetRefAtom = _reader.NameTable.Add("sheet"); - int relativeSheetId = 0; - while (await _reader.ReadAsync().ConfigureAwait(false) - && !_reader.EOF + string nameRefAtom = _readerWb.NameTable.Add("name"); + string sheetRefAtom = _readerWb.NameTable.Add("sheet"); + string idRefAtom = _readerWb.NameTable.Add("r:id"); + const string xl = "xl/"; + + while (_readerWb.Read() + && !_readerWb.EOF && !ct.IsCancellationRequested ) { - if (_reader.NodeType == XmlNodeType.Element - && _reader.LocalName == sheetRefAtom) + if (_readerWb.NodeType == XmlNodeType.Element + && _readerWb.LocalName == sheetRefAtom) { - if (_reader.MoveToAttribute(nameRefAtom)) + if (_readerWb.MoveToAttribute(nameRefAtom)) { - relativeSheetId++; - // `r:id` and `sheetId` are not to be trusted - yield return new KeyValuePair(_reader.Value, relativeSheetId); + string sheetName = _readerWb.Value; + if (_readerWb.MoveToAttribute(idRefAtom)) + { + yield return new KeyValuePair(sheetName, xl + worksheetRels[_readerWb.Value]); + } } } } } - public IEnumerable> GetSheetNames([EnumeratorCancellation] CancellationToken ct) + private IReadOnlyDictionary PopulateWorkSheetRels(CancellationToken ct) { - string sheetsRefAtom = _reader.NameTable.Add("sheets"); - if (!_reader.ReadToFollowing(sheetsRefAtom)) + using Stream? streamRelWb = _zipReader.GetEntry("xl/_rels/workbook.xml.rels"); + using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings { - yield break; + DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources + IgnoreComments = true, // Skip parsing and allocating strings for comments + IgnoreWhitespace = true, // Ignore significant whitespace + CheckCharacters = false, + CloseInput = true, + ConformanceLevel = ConformanceLevel.Document, + //NameTable = new WorkBookRelsRestrictedNameTable(), + ValidationType = ValidationType.None, + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + Async = true // TBD + }); + Dictionary worksheetRels = []; + string relationshipsRefAtom = readerRels.NameTable.Add("Relationships"); + if (!readerRels.ReadToFollowing(relationshipsRefAtom)) + { + return worksheetRels; } - - string nameRefAtom = _reader.NameTable.Add("name"); - string sheetRefAtom = _reader.NameTable.Add("sheet"); - int relativeSheetId = 0; - while (_reader.Read() - && !_reader.EOF + string relationshipRefAtom = readerRels.NameTable.Add("Relationship"); + string idRefAtom = readerRels.NameTable.Add("Id"); + string targetRefAtom = readerRels.NameTable.Add("Target"); + while (readerRels.Read() + && !readerRels.EOF && !ct.IsCancellationRequested ) { - if (_reader.NodeType == XmlNodeType.Element - && _reader.LocalName == sheetRefAtom) + if (readerRels.NodeType == XmlNodeType.Element + && readerRels.LocalName == relationshipRefAtom) { - if (_reader.MoveToAttribute(nameRefAtom)) + string id = string.Empty; + string target = string.Empty; + int expectedAttributes = 2; + + while (readerRels.MoveToNextAttribute() && expectedAttributes > 0) + { + // Retrieve the atomized name directly. + string currentAttributeName = readerRels.LocalName; + if (ReferenceEquals(currentAttributeName, idRefAtom)) + { + id = readerRels.Value; + expectedAttributes--; + } + else if (ReferenceEquals(currentAttributeName, targetRefAtom)) + { + target = readerRels.Value; + expectedAttributes--; + } + } + if (expectedAttributes == 0) { - relativeSheetId++; - // `r:id` and `sheetId` are not to be trusted - yield return new KeyValuePair(_reader.Value, relativeSheetId); + worksheetRels[id] = target; } } } + return worksheetRels; } - public async Task> GetDefinedRangesAsync( - IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct) + public IReadOnlyDictionary GetDefinedRanges( + IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct) { - string definedNamesRefAtom = _reader.NameTable.Add("definedNames"); + string definedNamesRefAtom = _readerWb.NameTable.Add("definedNames"); Dictionary definedRanges = []; - if (!_reader.ReadToFollowing(definedNamesRefAtom)) + if (!_readerWb.ReadToFollowing(definedNamesRefAtom)) { definedRanges.TrimExcess(); return definedRanges; } - string definedNameRefAtom = _reader.NameTable.Add("definedName"); - string nameRefAtom = _reader.NameTable.Add("name"); - string localSheetIdRefAtom = _reader.NameTable.Add("localSheetId"); + string definedNameRefAtom = _readerWb.NameTable.Add("definedName"); + string nameRefAtom = _readerWb.NameTable.Add("name"); + string localSheetIdRefAtom = _readerWb.NameTable.Add("localSheetId"); List? sheetRefs = null; - while (await _reader.ReadAsync().ConfigureAwait(false) - && !_reader.EOF + while (_readerWb.Read() + && !_readerWb.EOF && !ct.IsCancellationRequested ) { - if (_reader.NodeType == XmlNodeType.Element - && _reader.LocalName == definedNameRefAtom) + if (_readerWb.NodeType == XmlNodeType.Element + && _readerWb.LocalName == definedNameRefAtom) { string name = string.Empty; string localSheetId = string.Empty; int expectedAttributes = 2; - while (_reader.MoveToNextAttribute() && expectedAttributes > 0) + while (_readerWb.MoveToNextAttribute() && expectedAttributes > 0) { // Retrieve the atomized name directly. - string currentAttributeName = _reader.LocalName; + string currentAttributeName = _readerWb.LocalName; if (ReferenceEquals(currentAttributeName, nameRefAtom)) { - name = _reader.Value; + name = _readerWb.Value; expectedAttributes--; } else if (ReferenceEquals(currentAttributeName, localSheetIdRefAtom)) { - localSheetId = _reader.Value; + localSheetId = _readerWb.Value; expectedAttributes--; } } @@ -149,8 +196,8 @@ public async Task> GetDefinedRangesAsy } // Move to data - await _reader.ReadAsync().ConfigureAwait(false); - definedRanges[keyName] = new DefinedRange(_reader.Value) { Name = name, SheetIdReference = sheetRef }; + _readerWb.Read(); + definedRanges[keyName] = new DefinedRange(_readerWb.Value) { Name = name }; // Handle this situation-> if (definedRanges[keyName].SheetName == sheetRef) { @@ -163,44 +210,180 @@ public async Task> GetDefinedRangesAsy return definedRanges; } - public IReadOnlyDictionary GetDefinedRanges(IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct) + private void Dispose(bool isDisposing) { - string definedNamesRefAtom = _reader.NameTable.Add("definedNames"); + if (!_isDisposed) + { + if (isDisposing) + { + _readerWb.Dispose(); + _streamWb?.Dispose(); + } + + _isDisposed = true; + } + } + + ~XmlWorkBookReader() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(isDisposing: true); + GC.SuppressFinalize(this); + } +} + +internal class XmlWorkBookReaderAsync : XmlWorkBookReader, IOpenXmlWorkBookReaderAsync +{ + private IZipReaderAsync _zipReaderA => (IZipReaderAsync)base._zipReader; + + public XmlWorkBookReaderAsync(IZipReaderAsync zipReader, CancellationToken ct) + : base(zipReader) + { + _streamWb = zipReader.GetEntryAsync("xl/workbook.xml", ct).GetAwaiter().GetResult()!; + OpenWorkbookStream(); + } + + public async IAsyncEnumerable> GetSheetNamesAsync([EnumeratorCancellation] CancellationToken ct) + { + IReadOnlyDictionary worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false); + + string sheetsRefAtom = _readerWb.NameTable.Add("sheets"); + if (!worksheetRels.Any() + || !_readerWb.ReadToFollowing(sheetsRefAtom)) + { + yield break; + } + + string nameRefAtom = _readerWb.NameTable.Add("name"); + string sheetRefAtom = _readerWb.NameTable.Add("sheet"); + string idRefAtom = _readerWb.NameTable.Add("r:id"); + const string xl = "xl/"; + while (await _readerWb.ReadAsync().ConfigureAwait(false) + && !_readerWb.EOF + && !ct.IsCancellationRequested + ) + { + if (_readerWb.NodeType == XmlNodeType.Element + && _readerWb.LocalName == sheetRefAtom) + { + if (_readerWb.MoveToAttribute(nameRefAtom)) + { + string sheetName = _readerWb.Value; + if (_readerWb.MoveToAttribute(idRefAtom)) + { + yield return new KeyValuePair(sheetName, xl + worksheetRels[_readerWb.Value]); + } + } + } + } + } + + private async Task> 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 + { + DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources + IgnoreComments = true, // Skip parsing and allocating strings for comments + IgnoreWhitespace = true, // Ignore significant whitespace + CheckCharacters = false, + CloseInput = true, + ConformanceLevel = ConformanceLevel.Document, + //NameTable = new WorkBookRelsRestrictedNameTable(), + ValidationType = ValidationType.None, + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + Async = true // TBD + }); + Dictionary worksheetRels = []; + string relationshipsRefAtom = readerRels.NameTable.Add("Relationships"); + if (!readerRels.ReadToFollowing(relationshipsRefAtom)) + { + return worksheetRels; + } + string relationshipRefAtom = readerRels.NameTable.Add("Relationship"); + string idRefAtom = readerRels.NameTable.Add("Id"); + string targetRefAtom = readerRels.NameTable.Add("Target"); + while (await readerRels.ReadAsync().ConfigureAwait(false) + && !readerRels.EOF + && !ct.IsCancellationRequested + ) + { + if (readerRels.NodeType == XmlNodeType.Element + && readerRels.LocalName == relationshipRefAtom) + { + string id = string.Empty; + string target = string.Empty; + int expectedAttributes = 2; + + while (readerRels.MoveToNextAttribute() && expectedAttributes > 0) + { + // Retrieve the atomized name directly. + string currentAttributeName = readerRels.LocalName; + if (ReferenceEquals(currentAttributeName, idRefAtom)) + { + id = readerRels.Value; + expectedAttributes--; + } + else if (ReferenceEquals(currentAttributeName, targetRefAtom)) + { + target = readerRels.Value; + expectedAttributes--; + } + } + if (expectedAttributes == 0) + { + worksheetRels[id] = target; + } + } + } + return worksheetRels; + } + + public async Task> GetDefinedRangesAsync( + IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct) + { + string definedNamesRefAtom = _readerWb.NameTable.Add("definedNames"); Dictionary definedRanges = []; - if (!_reader.ReadToFollowing(definedNamesRefAtom)) + if (!_readerWb.ReadToFollowing(definedNamesRefAtom)) { definedRanges.TrimExcess(); return definedRanges; } - string definedNameRefAtom = _reader.NameTable.Add("definedName"); - string nameRefAtom = _reader.NameTable.Add("name"); - string localSheetIdRefAtom = _reader.NameTable.Add("localSheetId"); + string definedNameRefAtom = _readerWb.NameTable.Add("definedName"); + string nameRefAtom = _readerWb.NameTable.Add("name"); + string localSheetIdRefAtom = _readerWb.NameTable.Add("localSheetId"); List? sheetRefs = null; - while (_reader.Read() - && !_reader.EOF + while (await _readerWb.ReadAsync().ConfigureAwait(false) + && !_readerWb.EOF && !ct.IsCancellationRequested ) { - if (_reader.NodeType == XmlNodeType.Element - && _reader.LocalName == definedNameRefAtom) + if (_readerWb.NodeType == XmlNodeType.Element + && _readerWb.LocalName == definedNameRefAtom) { string name = string.Empty; string localSheetId = string.Empty; int expectedAttributes = 2; - while (_reader.MoveToNextAttribute() && expectedAttributes > 0) + while (_readerWb.MoveToNextAttribute() && expectedAttributes > 0) { // Retrieve the atomized name directly. - string currentAttributeName = _reader.LocalName; + string currentAttributeName = _readerWb.LocalName; if (ReferenceEquals(currentAttributeName, nameRefAtom)) { - name = _reader.Value; + name = _readerWb.Value; expectedAttributes--; } else if (ReferenceEquals(currentAttributeName, localSheetIdRefAtom)) { - localSheetId = _reader.Value; + localSheetId = _readerWb.Value; expectedAttributes--; } } @@ -219,8 +402,8 @@ public IReadOnlyDictionary GetDefinedRanges(IReadOnlyDicti } // Move to data - _reader.Read(); - definedRanges[keyName] = new DefinedRange(_reader.Value) { Name = name, SheetIdReference = sheetRef }; + await _readerWb.ReadAsync().ConfigureAwait(false); + definedRanges[keyName] = new DefinedRange(_readerWb.Value) { Name = name }; // Handle this situation-> if (definedRanges[keyName].SheetName == sheetRef) { @@ -233,30 +416,4 @@ public IReadOnlyDictionary GetDefinedRanges(IReadOnlyDicti return definedRanges; } - private void Dispose(bool isDisposing) - { - if (!_isDisposed) - { - if (isDisposing) - { - _reader.Dispose(); - _stream?.Dispose(); - } - - _isDisposed = true; - } - } - - ~XmlWorkBookReader() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(false); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(isDisposing: true); - GC.SuppressFinalize(this); - } -} \ No newline at end of file +} diff --git a/Excel_PRIME/Package_ReadMe.md b/Excel_PRIME/Package_ReadMe.md index 6a8371c..4d9803e 100644 --- a/Excel_PRIME/Package_ReadMe.md +++ b/Excel_PRIME/Package_ReadMe.md @@ -6,8 +6,8 @@ # What does that mean? - _Yet another Excel reader ?_, - Starting with .Net 8 as the performant Runtime (See Benchmarks) - - V9 gives an extra 5% boost, - - V10 Another 5% over 9 ;-) + - .Net9 gives an extra 5% boost, + - .Net10 Another 5% over .Net9 ;-) Lets take each of the above elements and explain: diff --git a/Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs b/Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs index 1be058f..1852f75 100644 --- a/Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs +++ b/Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs @@ -83,18 +83,10 @@ public ISharedString GetSharedStrings(IZipReader zipReader, bool optionsAccessEx /// - public async Task CreateWorkBookReaderAsync(IZipReaderAsync zipReader, CancellationToken ct) - { - Stream? stream = await zipReader.GetEntryAsync("xl/workbook.bin", ct).ConfigureAwait(false); - return new XlsbWorkBookReader(stream!, ct); - } + public async Task CreateWorkBookReaderAsync(IZipReaderAsync zipReader, CancellationToken ct) => new XlsbWorkBookReaderAsync(zipReader, ct); /// - public IOpenXmlWorkBookReader CreateWorkBookReader(IZipReader zipReader, CancellationToken ct) - { - Stream? stream = zipReader.GetEntry("xl/workbook.bin"); - return new XlsbWorkBookReader(stream!, ct); - } + public IOpenXmlWorkBookReader CreateWorkBookReader(IZipReader zipReader, CancellationToken ct) => new XlsbWorkBookReader(zipReader, ct); /// diff --git a/Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs b/Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs index 7899519..747aff4 100644 --- a/Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs +++ b/Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs @@ -3,34 +3,48 @@ using System.Collections.ObjectModel; using System.Globalization; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using System.Xml; using ExcelPRIME.FromExternal; using ExcelPRIME.XlsbImp; namespace ExcelPRIME.Implementation; -internal sealed class XlsbWorkBookReader : IOpenXmlWorkBookReaderAsync +internal class XlsbWorkBookReader : IOpenXmlWorkBookReader { - private readonly BufferedStream _stream; - private readonly XlsbStreamReader _reader; + private protected IZipReader _zipReader; + private protected BufferedStream _streamWb; + private protected XlsbStreamReader _readerWb; private bool _isDisposed; - public XlsbWorkBookReader(Stream stream, CancellationToken _) + protected XlsbWorkBookReader(IZipReader zipReader) => _zipReader = zipReader; + + public XlsbWorkBookReader(IZipReader zipReader, CancellationToken _) + : this(zipReader) { + Stream? stream = zipReader.GetEntry("xl/workbook.bin"); // For modern hardware in 2025, 65536(64KB) is the standard "sweet spot" for many workloads - _stream = new BufferedStream(stream!, 64 * 1024); - _reader = new XlsbStreamReader(_stream); + _streamWb = new BufferedStream(stream!, 64 * 1024); + OpenWorkbookStream(); } - public async IAsyncEnumerable> GetSheetNamesAsync( - [EnumeratorCancellation] CancellationToken ct) + protected void OpenWorkbookStream() => + _readerWb = new XlsbStreamReader(_streamWb); + + public IEnumerable> GetSheetNames([EnumeratorCancellation] CancellationToken ct) { - int relativeSheetId = 0; + IReadOnlyDictionary worksheetRels = PopulateWorkSheetRels(ct); + if (!worksheetRels.Any()) + { + yield break; + } - PooledRecordBuffer nextRecord = await _reader.ReadNextRecordAsync(ct).ConfigureAwait(false); + PooledRecordBuffer nextRecord = _readerWb.ReadNextRecord(); + const string xl = "xl/"; bool foundSheets = false; while (nextRecord.Succeeded && !ct.IsCancellationRequested) @@ -39,23 +53,21 @@ public async IAsyncEnumerable> GetSheetNamesAsync( { foundSheets = true; string? rel = nextRecord.GetString(8, out int next); - string name = nextRecord.GetString(next); + string sheetName = nextRecord.GetString(next); if (rel == null) { // no sheet rel means it is a macro. } else { - relativeSheetId++; - // `r:id` and `sheetId` are not to be trusted - yield return new KeyValuePair(name, relativeSheetId); + yield return new KeyValuePair(sheetName, xl + worksheetRels[rel]); } } - nextRecord = await _reader.ReadNextRecordAsync(ct).ConfigureAwait(false); + nextRecord = _readerWb.ReadNextRecord(); if (foundSheets && nextRecord.RecordType != RecordTypeIdentifier.BUNDLESHEET - ) + ) { break; } @@ -63,50 +75,74 @@ public async IAsyncEnumerable> GetSheetNamesAsync( nextRecord.Dispose(); } - public IEnumerable> GetSheetNames([EnumeratorCancellation] CancellationToken ct) + private IReadOnlyDictionary PopulateWorkSheetRels(CancellationToken ct) { - int relativeSheetId = 0; - - PooledRecordBuffer nextRecord = _reader.ReadNextRecord(); - bool foundSheets = false; - while (nextRecord.Succeeded - && !ct.IsCancellationRequested) + using Stream? streamRelWb = _zipReader.GetEntry("xl/_rels/workbook.bin.rels"); + using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings { - if (nextRecord.RecordType == RecordTypeIdentifier.BUNDLESHEET) + DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources + IgnoreComments = true, // Skip parsing and allocating strings for comments + IgnoreWhitespace = true, // Ignore significant whitespace + CheckCharacters = false, + CloseInput = true, + ConformanceLevel = ConformanceLevel.Document, + //NameTable = new WorkBookRelsRestrictedNameTable(), + ValidationType = ValidationType.None, + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + Async = true // TBD + }); + Dictionary worksheetRels = []; + string relationshipsRefAtom = readerRels.NameTable.Add("Relationships"); + if (!readerRels.ReadToFollowing(relationshipsRefAtom)) + { + return worksheetRels; + } + string relationshipRefAtom = readerRels.NameTable.Add("Relationship"); + string idRefAtom = readerRels.NameTable.Add("Id"); + string targetRefAtom = readerRels.NameTable.Add("Target"); + while (readerRels.Read() + && !readerRels.EOF + && !ct.IsCancellationRequested + ) + { + if (readerRels.NodeType == XmlNodeType.Element + && readerRels.LocalName == relationshipRefAtom) { - foundSheets = true; - string? rel = nextRecord.GetString(8, out int next); - string name = nextRecord.GetString(next); - if (rel == null) + string id = string.Empty; + string target = string.Empty; + int expectedAttributes = 2; + + while (readerRels.MoveToNextAttribute() && expectedAttributes > 0) { - // no sheet rel means it is a macro. + // Retrieve the atomized name directly. + string currentAttributeName = readerRels.LocalName; + if (ReferenceEquals(currentAttributeName, idRefAtom)) + { + id = readerRels.Value; + expectedAttributes--; + } + else if (ReferenceEquals(currentAttributeName, targetRefAtom)) + { + target = readerRels.Value; + expectedAttributes--; + } } - else + if (expectedAttributes == 0) { - relativeSheetId++; - // `r:id` and `sheetId` are not to be trusted - yield return new KeyValuePair(name, relativeSheetId); + worksheetRels[id] = target; } } - - nextRecord = _reader.ReadNextRecord(); - if (foundSheets - && nextRecord.RecordType != RecordTypeIdentifier.BUNDLESHEET - ) - { - break; - } } - nextRecord.Dispose(); + return worksheetRels; } - public async Task> GetDefinedRangesAsync( - IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct) + public IReadOnlyDictionary GetDefinedRanges( + IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct) { Dictionary definedRanges = []; List? sheetRefs = null; - PooledRecordBuffer nextRecord = await _reader.ReadNextRecordAsync(ct).ConfigureAwait(false); + PooledRecordBuffer nextRecord = _readerWb.ReadNextRecord(); bool foundDefinedNames = false; while (nextRecord.Succeeded && !ct.IsCancellationRequested) @@ -117,19 +153,18 @@ public async Task> GetDefinedRangesAsy int localSheetId = nextRecord.GetInt32(5); string name = nextRecord.GetString(9, out int formulaBegin)!; - (string columnStart, string columnEnd, int rowStart, int rowEnd, bool isNumber, short sheetRef) = DecodeNameParsedFormula(nextRecord, formulaBegin); - if (sheetRef < 0 - && localSheetId > -1) + if (sheetRef >= 0 + && localSheetId == -1) { - sheetRef = (short)localSheetId; + localSheetId = sheetRef; } string keyName = name; string sheetNameRef = string.Empty; - if (sheetRef > -1) + if (localSheetId != -1) { sheetRefs ??= [.. sheetNamesToOffsetSheetId.Keys]; - sheetNameRef = sheetRefs[sheetRef]; + sheetNameRef = sheetRefs[localSheetId]; if (!string.IsNullOrEmpty(sheetNameRef)) { keyName = string.Concat(name, " (", sheetNameRef, ")"); @@ -137,15 +172,15 @@ public async Task> GetDefinedRangesAsy } definedRanges[keyName] = isNumber - ? new DefinedRange(columnStart) { Name = name, SheetIdReference = sheetNameRef } - : new DefinedRange(sheetNameRef, columnStart, columnEnd, rowStart, rowEnd) { Name = name, SheetIdReference = sheetNameRef }; - if (localSheetId < 0) + ? new DefinedRange(columnStart) { Name = name } + : new DefinedRange(sheetNameRef, columnStart, columnEnd, rowStart, rowEnd) { Name = name}; + if (definedRanges[keyName].SheetName == sheetNameRef) { definedRanges.TryAdd(name, definedRanges[keyName]); } } - nextRecord = await _reader.ReadNextRecordAsync(ct).ConfigureAwait(false); + nextRecord = _readerWb.ReadNextRecord(); if (foundDefinedNames && nextRecord.RecordType != RecordTypeIdentifier.BRTNAME) { @@ -158,13 +193,224 @@ public async Task> GetDefinedRangesAsy return new ReadOnlyDictionary(definedRanges); } - public IReadOnlyDictionary GetDefinedRanges( - IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct) + private static (string columnStart, string columnEnd, int rowStart, int rowEnd, bool isNumber, short sheetRef) DecodeNameParsedFormula(PooledRecordBuffer nextRecord, int formulaBegin) + { + int cce = nextRecord.GetInt32(formulaBegin); + // PtgRef -> 0x24 + // PtgArea -> 0x25 + // PtgRefN -> 0x2C + // PtgAreaN -> 0x2D + // PtgRef -> 0x44 + // PtgArea -> 0x45 + // PtgRef -> 0x64 + // PtgArea -> 0x65 + + int offset = formulaBegin + 4; + byte ptg = nextRecord.GetByte(offset); + short sheetRef = -1; + offset++; // Step over Ptg### preamble + + switch (ptg) + { + case 0x1D: // ptgBool + return ((nextRecord.GetByte(offset) != 0).ToString(CultureInfo.InvariantCulture), string.Empty, 0, 0, true, sheetRef); + case 0x1E: // ptgInt + return (nextRecord.GetInt16(offset).ToString(CultureInfo.InvariantCulture), string.Empty, 0, 0, true, sheetRef); + case 0x1F: // ptgNum + return (nextRecord.GetDouble(offset).ToString(CultureInfo.InvariantCulture), string.Empty, 0, 0, true, sheetRef); + + case 0x25: //PtgArea + case 0x45: + case 0x65: + break; + + case 0x3A: // PtgRef3d + case 0x5A: + case 0x7A: + { + sheetRef = nextRecord.GetInt16(offset); + offset += 2; // Step over PtgArea3d `SheetRef` + int row = nextRecord.GetInt32(offset) + 1; + offset += 4; + int col = nextRecord.GetInt16(offset) + 1; + return (col.GetExcelColumnName(), col.GetExcelColumnName(), row, row, false, sheetRef); + } + + case 0x3B: //PtgArea3d + case 0x5B: + case 0x7B: + sheetRef = nextRecord.GetInt16(offset); + offset += 2; // Step over PtgArea3d `SheetRef` + break; + + default: // 0x23(35) -> PtgName | 0x39(57) -> PtgNameX + return (string.Empty, string.Empty, 0, 0, false, sheetRef); + } + int rowFirst = nextRecord.GetInt32(offset) + 1; + offset += 4; + int rowLast = nextRecord.GetInt32(offset) + 1; + offset += 4; + int colFirst = nextRecord.GetInt16(offset) + 1; + offset += 2; + int colLast = nextRecord.GetInt16(offset) + 1; + //offset = formulaBegin + cce; + //int cb = nextRecord.GetInt32(offset); + //if (cb > 0) + //{ + // sheetRef = nextRecord.GetInt16(offset + 5); + //} + + return (colFirst.GetExcelColumnName(), colLast.GetExcelColumnName(), rowFirst, rowLast, false, sheetRef); + } + + private void Dispose(bool isDisposing) + { + if (!_isDisposed) + { + if (isDisposing) + { + _streamWb?.Dispose(); + } + + _isDisposed = true; + } + } + + ~XlsbWorkBookReader() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(isDisposing: true); + GC.SuppressFinalize(this); + } +} + +internal class XlsbWorkBookReaderAsync : XlsbWorkBookReader, IOpenXmlWorkBookReaderAsync +{ + private IZipReaderAsync _zipReaderA => (IZipReaderAsync)base._zipReader; + + public XlsbWorkBookReaderAsync(IZipReaderAsync zipReader, CancellationToken ct) + : base(zipReader) + { + Stream? stream = zipReader.GetEntryAsync("xl/workbook.bin", ct).GetAwaiter().GetResult(); + // For modern hardware in 2025, 65536(64KB) is the standard "sweet spot" for many workloads + _streamWb = new BufferedStream(stream!, 64 * 1024); + OpenWorkbookStream(); + } + + public async IAsyncEnumerable> GetSheetNamesAsync([EnumeratorCancellation] CancellationToken ct) + { + IReadOnlyDictionary worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false); + if (!worksheetRels.Any()) + { + yield break; + } + + PooledRecordBuffer nextRecord = await _readerWb.ReadNextRecordAsync(ct).ConfigureAwait(false); + bool foundSheets = false; + const string xl = "xl/"; + while (nextRecord.Succeeded + && !ct.IsCancellationRequested) + { + if (nextRecord.RecordType == RecordTypeIdentifier.BUNDLESHEET) + { + foundSheets = true; + string? rel = nextRecord.GetString(8, out int next); + string sheetName = nextRecord.GetString(next); + if (rel == null) + { + // no sheet rel means it is a macro. + } + else + { + yield return new KeyValuePair(sheetName, xl + worksheetRels[rel]); + } + } + + nextRecord = await _readerWb.ReadNextRecordAsync(ct).ConfigureAwait(false); + if (foundSheets + && nextRecord.RecordType != RecordTypeIdentifier.BUNDLESHEET + ) + { + break; + } + } + nextRecord.Dispose(); + } + + private async Task> 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 + { + DtdProcessing = DtdProcessing.Prohibit, // Disable DTDs for untrusted sources + IgnoreComments = true, // Skip parsing and allocating strings for comments + IgnoreWhitespace = true, // Ignore significant whitespace + CheckCharacters = false, + CloseInput = true, + ConformanceLevel = ConformanceLevel.Document, + //NameTable = new WorkBookRelsRestrictedNameTable(), + ValidationType = ValidationType.None, + ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None, + Async = true // TBD + }); + Dictionary worksheetRels = []; + string relationshipsRefAtom = readerRels.NameTable.Add("Relationships"); + if (!readerRels.ReadToFollowing(relationshipsRefAtom)) + { + return worksheetRels; + } + string relationshipRefAtom = readerRels.NameTable.Add("Relationship"); + string idRefAtom = readerRels.NameTable.Add("Id"); + string targetRefAtom = readerRels.NameTable.Add("Target"); + while (await readerRels.ReadAsync().ConfigureAwait(false) + && !readerRels.EOF + && !ct.IsCancellationRequested + ) + { + if (readerRels.NodeType == XmlNodeType.Element + && readerRels.LocalName == relationshipRefAtom) + { + string id = string.Empty; + string target = string.Empty; + int expectedAttributes = 2; + + while (readerRels.MoveToNextAttribute() && expectedAttributes > 0) + { + // Retrieve the atomized name directly. + string currentAttributeName = readerRels.LocalName; + if (ReferenceEquals(currentAttributeName, idRefAtom)) + { + id = readerRels.Value; + expectedAttributes--; + } + else if (ReferenceEquals(currentAttributeName, targetRefAtom)) + { + target = readerRels.Value; + expectedAttributes--; + } + } + if (expectedAttributes == 0) + { + worksheetRels[id] = target; + } + } + } + return worksheetRels; + } + + public async Task> GetDefinedRangesAsync( + IReadOnlyDictionary sheetNamesToOffsetSheetId, CancellationToken ct) { Dictionary definedRanges = []; List? sheetRefs = null; - PooledRecordBuffer nextRecord = _reader.ReadNextRecord(); + PooledRecordBuffer nextRecord = await _readerWb.ReadNextRecordAsync(ct).ConfigureAwait(false); bool foundDefinedNames = false; while (nextRecord.Succeeded && !ct.IsCancellationRequested) @@ -175,18 +421,19 @@ public IReadOnlyDictionary GetDefinedRanges( int localSheetId = nextRecord.GetInt32(5); string name = nextRecord.GetString(9, out int formulaBegin)!; + (string columnStart, string columnEnd, int rowStart, int rowEnd, bool isNumber, short sheetRef) = DecodeNameParsedFormula(nextRecord, formulaBegin); - if (sheetRef >= 0 - && localSheetId == -1) + if (sheetRef < 0 + && localSheetId > -1) { - localSheetId = sheetRef; + sheetRef = (short)localSheetId; } string keyName = name; string sheetNameRef = string.Empty; - if (localSheetId != -1) + if (sheetRef > -1) { sheetRefs ??= [.. sheetNamesToOffsetSheetId.Keys]; - sheetNameRef = sheetRefs[localSheetId]; + sheetNameRef = sheetRefs[sheetRef]; if (!string.IsNullOrEmpty(sheetNameRef)) { keyName = string.Concat(name, " (", sheetNameRef, ")"); @@ -194,15 +441,15 @@ public IReadOnlyDictionary GetDefinedRanges( } definedRanges[keyName] = isNumber - ? new DefinedRange(columnStart) { Name = name, SheetIdReference = sheetNameRef } - : new DefinedRange(sheetNameRef, columnStart, columnEnd, rowStart, rowEnd) { Name = name, SheetIdReference = sheetNameRef }; - if (definedRanges[keyName].SheetName == sheetNameRef) + ? new DefinedRange(columnStart) { Name = name } + : new DefinedRange(sheetNameRef, columnStart, columnEnd, rowStart, rowEnd) { Name = name }; + if (localSheetId < 0) { definedRanges.TryAdd(name, definedRanges[keyName]); } } - nextRecord = _reader.ReadNextRecord(); + nextRecord = await _readerWb.ReadNextRecordAsync(ct).ConfigureAwait(false); if (foundDefinedNames && nextRecord.RecordType != RecordTypeIdentifier.BRTNAME) { @@ -286,30 +533,4 @@ private static (string columnStart, string columnEnd, int rowStart, int rowEnd, string columnName = colLast.GetExcelColumnName(); return (colFirst.GetExcelColumnName(), columnName, rowFirst, rowLast, false, sheetRef); } - - private void Dispose(bool isDisposing) - { - if (!_isDisposed) - { - if (isDisposing) - { - _stream?.Dispose(); - } - - _isDisposed = true; - } - } - - ~XlsbWorkBookReader() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(false); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(isDisposing: true); - GC.SuppressFinalize(this); - } -} \ No newline at end of file +} diff --git a/Excel_PRIMEXlsb.RangeBench/GetRangeExcelPrimeXlsb.cs b/Excel_PRIMEXlsb.RangeBench/GetRangeExcelPrimeXlsb.cs index db72e1e..1255661 100644 --- a/Excel_PRIMEXlsb.RangeBench/GetRangeExcelPrimeXlsb.cs +++ b/Excel_PRIMEXlsb.RangeBench/GetRangeExcelPrimeXlsb.cs @@ -20,11 +20,9 @@ public bool LoadFile(string fullPath) } - public IEnumerable> GetDefinedRange(string definedName, int? localSheetId = null) + public IEnumerable> GetDefinedRange(string definedName, string? sheetName = null) { - IEnumerable rangeRows = localSheetId.HasValue - ? _workbook.GetDefinedRange(definedName, localSheetId.Value) - : _workbook.GetDefinedRange(definedName); + IEnumerable rangeRows = _workbook.GetDefinedRange(definedName, sheetName); foreach (CellValue?[] rangeRow in rangeRows) { yield return rangeRow.Select(cell => cell?.ToString()); diff --git a/Excel_PRIMEXlsb.RangeBench/XlsbRangeBenchmarks.cs b/Excel_PRIMEXlsb.RangeBench/XlsbRangeBenchmarks.cs index c777c70..3b76daf 100644 --- a/Excel_PRIMEXlsb.RangeBench/XlsbRangeBenchmarks.cs +++ b/Excel_PRIMEXlsb.RangeBench/XlsbRangeBenchmarks.cs @@ -35,15 +35,15 @@ public int Access100mb(Type ranger) //< definedName name = "DışVeri_1" localSheetId = "0" hidden = "1" > 'Worksheet (2)'!$A$1:$H$27001 //< definedName name = "DışVeri_1" localSheetId = "1" hidden = "1" > 'Worksheet (3)'!$A$1:$H$4001 //< definedName name = "DışVeri_2" localSheetId = "3" hidden = "1" > Worksheet!$A$952351:$H$985351 - IEnumerable> rangeTablo3 = getRanger.GetDefinedRange("DışVeri_1", 2); + IEnumerable> rangeTablo3 = getRanger.GetDefinedRange("DışVeri_1"); int cells = rangeTablo3.Sum(row => row.Count()); - IEnumerable> rangeWorksheet = getRanger.GetDefinedRange("DışVeri_1", 3); + IEnumerable> rangeWorksheet = getRanger.GetDefinedRange("DışVeri_1"); cells += rangeWorksheet.Sum(row => row.Count()); - IEnumerable> rangeWorksheet2 = getRanger.GetDefinedRange("DışVeri_1", 0); + IEnumerable> rangeWorksheet2 = getRanger.GetDefinedRange("DışVeri_1"); cells += rangeWorksheet2.Sum(row => row.Count()); - IEnumerable> rangeWorksheet3 = getRanger.GetDefinedRange("DışVeri_1", 1); + IEnumerable> rangeWorksheet3 = getRanger.GetDefinedRange("DışVeri_1"); cells += rangeWorksheet3.Sum(row => row.Count()); - IEnumerable> rangeDışVeri_2 = getRanger.GetDefinedRange("DışVeri_2", 3); + IEnumerable> rangeDışVeri_2 = getRanger.GetDefinedRange("DışVeri_2"); cells += rangeDışVeri_2.Sum(row => row.Count()); return cells; @@ -63,7 +63,7 @@ public int AccessPivotTable(Type ranger) getRanger.LoadFile("Data\\pivot-tables.xlsb"); // - IEnumerable> filterDatabaseSheet = getRanger.GetDefinedRange("_xlnm._FilterDatabase", 2); + IEnumerable> filterDatabaseSheet = getRanger.GetDefinedRange("_xlnm._FilterDatabase"); int cells = filterDatabaseSheet.Sum(row => row.Count()); IEnumerable> filterDatabase = getRanger.GetDefinedRange("_xlnm._FilterDatabase"); cells += filterDatabase.Sum(row => row.Count()); diff --git a/README.md b/README.md index 2229dd4..71025ce 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ # What does that mean? - _Yet another Excel reader ?_, - Starting with .Net 8 as the performant Runtime (See Benchmarks) - - V9 gives an extra 5% boost, - - V10 Another 5% ;-) + - .Net9 gives an extra 5% boost, + - .Net10 Another 5% over .Net9 ;-) Lets take each of the above elements and explain: @@ -261,9 +261,16 @@ Lets take each of the above elements and explain: - 🎊 Released **V3** as Nuget `V3.2601.11` ----- -## Phase 4 - Specific Cell value type(s) #️⃣ +## Phase V4 - Specific Cell value type(s) #️⃣ - Alpha +- ⛓️‍💥 **Breaking Change(s)** + - Removal of `GetSheetFileName(int offsetSheetId);` + - Removal of `GetDefinedRange` via `int sheetId` + - Removal of `Index` property from `ISheet` + - Internal Creation of WorkBooks + - Internal implementation of `IOpenXmlWorkBookReader::GetSheetNames` now returns the relative path to the sheetName - [ ] Cell object type 📅 - - [ ] `Operator` based conversion + - [ ] "Best Effort" `Operator` based conversion + - [ ] TryGet`Type` will return `out type`, if stored as that type. - [ ] Deal with `DateOnly` / `TimeOnly` fields -> `CellConversion.NumberAndDates` 💹 - [ ] Use of user defined column schema (Excel Number Format nuget?) - [ ] Formatter applied -> `CellConversion.ForceStyles` diff --git a/Release_Notes.md b/Release_Notes.md index 601786e..518a8c6 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -1,3 +1,11 @@ +# 2026-01-## - V4 - Alpha +- ⛓️‍💥 **Breaking Change(s)** + - Removal of `GetSheetFileName(int offsetSheetId);` + - Removal of `GetDefinedRange` via `int sheetId` + - Removal of `Index` property from `ISheet` + - Internal Creation of WorkBooks + - Internal implementation of `IOpenXmlWorkBookReader::GetSheetNames` now returns the relative path to the sheetName + # 2026-01-16 - V3 - Remove some `AggressiveOptimization` and allow `i-cache` to do its job - Implement "Hot-Paths" for cell type access