From 17d3e3f7d2fcde0ab79670ddc3f2f6adafee6d86 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Wed, 12 Nov 2025 19:04:37 +0100 Subject: [PATCH 1/2] Changing shared strings cache path from Guid based to TempPath based Also adding an option to OpenXmlConfiguration for manually overriding the path. --- .../OpenXml/OpenXmlConfiguration.cs | 10 +++++- src/MiniExcel.Core/OpenXml/OpenXmlReader.cs | 2 +- .../OpenXml/Utils/SharedStringsDiskCache.cs | 35 ++++++++++--------- .../MiniExcelIssueTests.cs | 21 +++++------ 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/MiniExcel.Core/OpenXml/OpenXmlConfiguration.cs b/src/MiniExcel.Core/OpenXml/OpenXmlConfiguration.cs index b3de0395..3a63df41 100644 --- a/src/MiniExcel.Core/OpenXml/OpenXmlConfiguration.cs +++ b/src/MiniExcel.Core/OpenXml/OpenXmlConfiguration.cs @@ -13,16 +13,24 @@ public class OpenXmlConfiguration : MiniExcelBaseConfiguration public int FreezeRowCount { get; set; } = 1; public int FreezeColumnCount { get; set; } = 0; public bool EnableConvertByteArray { get; set; } = true; + public bool EnableWriteFilePath{ get; set; } = true; public bool IgnoreTemplateParameterMissing { get; set; } = true; public bool EnableWriteNullValueCell { get; set; } = true; public bool WriteEmptyStringAsNull { get; set; } = false; public bool TrimColumnNames { get; set; } = true; public bool IgnoreEmptyRows { get; set; } = false; + public bool EnableSharedStringCache { get; set; } = true; public long SharedStringCacheSize { get; set; } = 5 * 1024 * 1024; + + /// + /// The directory where the shared strings cache files are stored. + /// It defaults to the system's temporary folder. + /// + public string SharedStringCachePath { get; set; } = Path.GetTempPath(); + public OpenXmlStyleOptions StyleOptions { get; set; } = new(); public DynamicExcelSheetAttribute[]? DynamicSheets { get; set; } - public bool EnableWriteFilePath{ get; set; } = true; /// /// Calculate column widths automatically from each column value. diff --git a/src/MiniExcel.Core/OpenXml/OpenXmlReader.cs b/src/MiniExcel.Core/OpenXml/OpenXmlReader.cs index f3ee77db..17be9ab8 100644 --- a/src/MiniExcel.Core/OpenXml/OpenXmlReader.cs +++ b/src/MiniExcel.Core/OpenXml/OpenXmlReader.cs @@ -420,7 +420,7 @@ private async Task SetSharedStringsAsync(CancellationToken cancellationToken = d #endif if (_config.EnableSharedStringCache && sharedStringsEntry.Length >= _config.SharedStringCacheSize) { - SharedStrings = new SharedStringsDiskCache(); + SharedStrings = new SharedStringsDiskCache(_config.SharedStringCachePath); await foreach (var sharedString in XmlReaderHelper.GetSharedStringsAsync(stream, cancellationToken, Ns).ConfigureAwait(false)) { SharedStrings[idx++] = sharedString; diff --git a/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs b/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs index bae9245c..b54654f5 100644 --- a/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs +++ b/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs @@ -2,17 +2,17 @@ internal class SharedStringsDiskCache : IDictionary, IDisposable { + private const int ExcelCellMaxLength = 32767; private static readonly Encoding Encoding = new UTF8Encoding(true); private readonly FileStream _positionFs; private readonly FileStream _lengthFs; private readonly FileStream _valueFs; - private long _maxIndx = -1; - + private long _maxIndex = -1; private bool _disposedValue; - public int Count => checked((int)(_maxIndx + 1)); + public int Count => checked((int)(_maxIndex + 1)); public string this[int key] { @@ -20,25 +20,28 @@ public string this[int key] set => Add(key, value); } - public bool ContainsKey(int key) => key <= _maxIndx; + public bool ContainsKey(int key) => key <= _maxIndex; - public SharedStringsDiskCache() + public SharedStringsDiskCache(string sharedStringsCachePath) { - var path = $"{Guid.NewGuid().ToString()}_miniexcelcache"; - - _positionFs = new FileStream($"{path}_position", FileMode.OpenOrCreate); - _lengthFs = new FileStream($"{path}_length", FileMode.OpenOrCreate); - _valueFs = new FileStream($"{path}_data", FileMode.OpenOrCreate); + var dir = Path.GetDirectoryName(sharedStringsCachePath); + if (!Directory.Exists(dir)) + throw new DirectoryNotFoundException($"\"{dir}\" is not a valid path for the shared strings cache."); + + var prefix = $"{Path.GetRandomFileName()}_miniexcel"; + _positionFs = new FileStream(Path.Combine(dir, $"{prefix}_position"), FileMode.OpenOrCreate); + _lengthFs = new FileStream(Path.Combine(dir, $"{prefix}_length"), FileMode.OpenOrCreate); + _valueFs = new FileStream(Path.Combine(dir, $"{prefix}_data"), FileMode.OpenOrCreate); } // index must start with 0-N - internal void Add(int index, string value) + private void Add(int index, string value) { - if (index > _maxIndx) - _maxIndx = index; + if (index > _maxIndex) + _maxIndex = index; var valueBs = Encoding.GetBytes(value); - if (value.Length > 32767) //check info length, becasue cell string max length is 47483647 + if (value.Length > ExcelCellMaxLength) //check info length, becasue cell string max length is 47483647 throw new ArgumentOutOfRangeException("", "Excel one cell max length is 32,767 characters"); _positionFs.Write(BitConverter.GetBytes(_valueFs.Position), 0, 4); @@ -105,13 +108,13 @@ public bool Remove(KeyValuePair item) public IEnumerator> GetEnumerator() { - for (int i = 0; i < _maxIndx; i++) + for (int i = 0; i < _maxIndex; i++) yield return new KeyValuePair(i, this[i]); } IEnumerator IEnumerable.GetEnumerator() { - for (int i = 0; i < _maxIndx; i++) + for (int i = 0; i < _maxIndex; i++) yield return this[i]; } diff --git a/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs index 1d64866b..2e4a8f42 100644 --- a/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs @@ -303,22 +303,19 @@ public void TestIssue360() [Fact] public void TestIssue117() { + var cacheFull = new SharedStringsDiskCache(Path.GetTempPath()); + for (int i = 0; i < 100; i++) { - var cache = new SharedStringsDiskCache(); - for (int i = 0; i < 100; i++) - { - cache[i] = i.ToString(); - } - for (int i = 0; i < 100; i++) - { - Assert.Equal(i.ToString(), cache[i]); - } - Assert.Equal(100, cache.Count); + cacheFull[i] = i.ToString(); } + for (int i = 0; i < 100; i++) { - var cache = new SharedStringsDiskCache(); - Assert.Empty(cache); + Assert.Equal(i.ToString(), cacheFull[i]); } + Assert.Equal(100, cacheFull.Count); + + var cacheEmpty = new SharedStringsDiskCache(Path.GetTempPath()); + Assert.Empty(cacheEmpty); } [Fact] From e09f691bdbaa16f10fdacf52311c4a5b72397c60 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Wed, 12 Nov 2025 19:38:03 +0100 Subject: [PATCH 2/2] Implementing code-assist's suggestions --- .../OpenXml/Utils/SharedStringsDiskCache.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs b/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs index b54654f5..bbc113bd 100644 --- a/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs +++ b/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs @@ -22,16 +22,15 @@ public string this[int key] public bool ContainsKey(int key) => key <= _maxIndex; - public SharedStringsDiskCache(string sharedStringsCachePath) + public SharedStringsDiskCache(string sharedStringsCacheDir) { - var dir = Path.GetDirectoryName(sharedStringsCachePath); - if (!Directory.Exists(dir)) - throw new DirectoryNotFoundException($"\"{dir}\" is not a valid path for the shared strings cache."); + if (string.IsNullOrWhiteSpace(sharedStringsCacheDir) || !Directory.Exists(sharedStringsCacheDir)) + throw new DirectoryNotFoundException($"\"{sharedStringsCacheDir}\" is not a valid directory for the shared strings cache."); var prefix = $"{Path.GetRandomFileName()}_miniexcel"; - _positionFs = new FileStream(Path.Combine(dir, $"{prefix}_position"), FileMode.OpenOrCreate); - _lengthFs = new FileStream(Path.Combine(dir, $"{prefix}_length"), FileMode.OpenOrCreate); - _valueFs = new FileStream(Path.Combine(dir, $"{prefix}_data"), FileMode.OpenOrCreate); + _positionFs = new FileStream(Path.Combine(sharedStringsCacheDir, $"{prefix}_position"), FileMode.OpenOrCreate); + _lengthFs = new FileStream(Path.Combine(sharedStringsCacheDir, $"{prefix}_length"), FileMode.OpenOrCreate); + _valueFs = new FileStream(Path.Combine(sharedStringsCacheDir, $"{prefix}_data"), FileMode.OpenOrCreate); } // index must start with 0-N @@ -108,13 +107,13 @@ public bool Remove(KeyValuePair item) public IEnumerator> GetEnumerator() { - for (int i = 0; i < _maxIndex; i++) + for (int i = 0; i <= _maxIndex; i++) yield return new KeyValuePair(i, this[i]); } IEnumerator IEnumerable.GetEnumerator() { - for (int i = 0; i < _maxIndex; i++) + for (int i = 0; i <= _maxIndex; i++) yield return this[i]; }