From 29a74465bad438c3e630f3c450b0ecb60f13cecc Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Sat, 15 Mar 2025 01:39:38 +0100 Subject: [PATCH 1/4] Enhanced methods Insert, SaveAs and relative async versione - The methods Insert / InsertAsync now return the number of rows inserted into the worksheet, while SaveAs / SaveAsAsync return an int array representing the rows inserted into each worksheet - Added asserts in tests to that check for aferomentioned return values to be correct, plus a little bit of tiding said tests - Minor code cleanup (denesting branches, renaming variables, turning methods that don't change state of the object into static functions...) --- src/MiniExcel/Csv/CsvWriter.cs | 109 +++++++---- src/MiniExcel/IExcelWriter.cs | 8 +- src/MiniExcel/MiniExcel.Async.cs | 56 +++--- src/MiniExcel/MiniExcel.cs | 56 +++--- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 170 +++++++++-------- .../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 90 +++------ .../OpenXml/ExcelOpenXmlSheetWriter.cs | 73 ++++---- .../OpenXml/MiniExcelAsyncStreamWriter.cs | 33 ++-- tests/MiniExcelTests/MiniExcelCsvAsycTests.cs | 145 +++++++++----- tests/MiniExcelTests/MiniExcelCsvTests.cs | 108 +++++++---- .../MiniExcelTests/MiniExcelIssue2024Tests.cs | 10 +- .../MiniExcelIssueAsyncTests.cs | 177 ++++++++++++------ tests/MiniExcelTests/MiniExcelIssueTests.cs | 56 +++--- .../MiniExcelOpenXmlMultipleSheetTests.cs | 5 +- tests/MiniExcelTests/MiniExcelOpenXmlTests.cs | 143 ++++++++------ 15 files changed, 715 insertions(+), 524 deletions(-) diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel/Csv/CsvWriter.cs index 54998b94..ea51edb3 100644 --- a/src/MiniExcel/Csv/CsvWriter.cs +++ b/src/MiniExcel/Csv/CsvWriter.cs @@ -18,33 +18,35 @@ internal class CsvWriter : IExcelWriter, IDisposable private readonly bool _printHeader; private object _value; private readonly StreamWriter _writer; - private bool disposedValue; + private bool _disposedValue; public CsvWriter(Stream stream, object value, IConfiguration configuration, bool printHeader) { - this._stream = stream; - this._configuration = configuration == null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration; - this._printHeader = printHeader; - this._value = value; - this._writer = _configuration.StreamWriterFunc(_stream); + _stream = stream; + _configuration = configuration == null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration; + _printHeader = printHeader; + _value = value; + _writer = _configuration.StreamWriterFunc(_stream); } - public void SaveAs() + public int[] SaveAs() { if (_value == null) { _writer.Write(""); _writer.Flush(); - return; + return new int[0]; } - WriteValues(_writer, _value); + var rowsWritten = WriteValues(_value); _writer.Flush(); + + return new[] { rowsWritten }; } - public void Insert(bool overwriteSheet = false) + public int Insert(bool overwriteSheet = false) { - SaveAs(); + return SaveAs().FirstOrDefault(); } private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column) @@ -53,12 +55,11 @@ private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column) rowBuilder.Append(_configuration.Seperator); } - private void RemoveTrailingSeparator(StringBuilder rowBuilder) + private static void RemoveTrailingSeparator(StringBuilder rowBuilder) { if (rowBuilder.Length == 0) - { return; - } + rowBuilder.Remove(rowBuilder.Length - 1, 1); } @@ -66,16 +67,16 @@ private string GetHeader(List props) => string.Join( _configuration.Seperator.ToString(), props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration.AlwaysQuote, _configuration.Seperator))); - private void WriteValues(StreamWriter writer, object values) + private int WriteValues(object values) { - IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + var writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); var props = writeAdapter.GetColumns(); if (props == null) { _writer.Write(_configuration.NewLine); _writer.Flush(); - return; + return 0; } if (_printHeader) @@ -84,25 +85,32 @@ private void WriteValues(StreamWriter writer, object values) _writer.Write(_configuration.NewLine); } + if (writeAdapter == null) + return 0; + var rowBuilder = new StringBuilder(); - if (writeAdapter != null) + var rowsWritten = 0; + + foreach (var row in writeAdapter.GetRows(props)) { - foreach (var row in writeAdapter.GetRows(props)) + rowBuilder.Clear(); + foreach (var column in row) { - rowBuilder.Clear(); - foreach (var column in row) - { - AppendColumn(rowBuilder, column); - } - RemoveTrailingSeparator(rowBuilder); - _writer.Write(rowBuilder.ToString()); - _writer.Write(_configuration.NewLine); + AppendColumn(rowBuilder, column); } + RemoveTrailingSeparator(rowBuilder); + _writer.Write(rowBuilder.ToString()); + _writer.Write(_configuration.NewLine); + + rowsWritten++; } + return rowsWritten; } - private async Task WriteValuesAsync(StreamWriter writer, object values, string seperator, string newLine, CancellationToken cancellationToken) + private async Task WriteValuesAsync(StreamWriter writer, object values, string seperator, string newLine, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + #if NETSTANDARD2_0_OR_GREATER || NET IMiniExcelWriteAdapter writeAdapter = null; if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) @@ -118,14 +126,18 @@ private async Task WriteValuesAsync(StreamWriter writer, object values, string s { await _writer.WriteAsync(_configuration.NewLine); await _writer.FlushAsync(); - return; + return 0; } + if (_printHeader) { await _writer.WriteAsync(GetHeader(props)); await _writer.WriteAsync(newLine); } + var rowBuilder = new StringBuilder(); + var rowsWritten = 0; + if (writeAdapter != null) { foreach (var row in writeAdapter.GetRows(props, cancellationToken)) @@ -133,11 +145,15 @@ private async Task WriteValuesAsync(StreamWriter writer, object values, string s rowBuilder.Clear(); foreach (var column in row) { + cancellationToken.ThrowIfCancellationRequested(); AppendColumn(rowBuilder, column); } + RemoveTrailingSeparator(rowBuilder); await _writer.WriteAsync(rowBuilder.ToString()); await _writer.WriteAsync(newLine); + + rowsWritten++; } } #if NETSTANDARD2_0_OR_GREATER || NET @@ -145,21 +161,30 @@ private async Task WriteValuesAsync(StreamWriter writer, object values, string s { await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); rowBuilder.Clear(); + await foreach (var column in row) { + cancellationToken.ThrowIfCancellationRequested(); AppendColumn(rowBuilder, column); } + RemoveTrailingSeparator(rowBuilder); await _writer.WriteAsync(rowBuilder.ToString()); await _writer.WriteAsync(newLine); + + rowsWritten++; } } #endif + return rowsWritten; } - public async Task SaveAsAsync(CancellationToken cancellationToken = default) + public async Task SaveAsAsync(CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); + var seperator = _configuration.Seperator.ToString(); var newLine = _configuration.NewLine; @@ -167,16 +192,19 @@ public async Task SaveAsAsync(CancellationToken cancellationToken = default) { await _writer.WriteAsync(""); await _writer.FlushAsync(); - return; + return new int[0]; } - await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken); + var rowsWritten = await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken); await _writer.FlushAsync(); + + return new[] { rowsWritten }; } - public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) + public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) { - await SaveAsAsync(cancellationToken); + var rowsWritten = await SaveAsAsync(cancellationToken); + return rowsWritten.FirstOrDefault(); } public string ToCsvString(object value, ExcelColumnInfo p) @@ -190,29 +218,30 @@ public string ToCsvString(object value, ExcelColumnInfo p) { return dateTime.ToString(p.ExcelFormat, _configuration.Culture); } - return _configuration.Culture.Equals(CultureInfo.InvariantCulture) ? dateTime.ToString("yyyy-MM-dd HH:mm:ss", _configuration.Culture) : dateTime.ToString(_configuration.Culture); + return _configuration.Culture.Equals(CultureInfo.InvariantCulture) + ? dateTime.ToString("yyyy-MM-dd HH:mm:ss", _configuration.Culture) + : dateTime.ToString(_configuration.Culture); } + if (p?.ExcelFormat != null && value is IFormattable formattableValue) - { return formattableValue.ToString(p.ExcelFormat, _configuration.Culture); - } return Convert.ToString(value, _configuration.Culture); } protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { - this._writer.Dispose(); + _writer.Dispose(); // TODO: dispose managed state (managed objects) } // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null - disposedValue = true; + _disposedValue = true; } } diff --git a/src/MiniExcel/IExcelWriter.cs b/src/MiniExcel/IExcelWriter.cs index be5195db..feea4171 100644 --- a/src/MiniExcel/IExcelWriter.cs +++ b/src/MiniExcel/IExcelWriter.cs @@ -5,9 +5,9 @@ namespace MiniExcelLibs { internal interface IExcelWriter { - void SaveAs(); - Task SaveAsAsync(CancellationToken cancellationToken = default); - void Insert(bool overwriteSheet = false); - Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default); + int[] SaveAs(); + Task SaveAsAsync(CancellationToken cancellationToken = default); + int Insert(bool overwriteSheet = false); + Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default); } } diff --git a/src/MiniExcel/MiniExcel.Async.cs b/src/MiniExcel/MiniExcel.Async.cs index 342dc0fb..2ded0e30 100644 --- a/src/MiniExcel/MiniExcel.Async.cs +++ b/src/MiniExcel/MiniExcel.Async.cs @@ -13,67 +13,57 @@ public static partial class MiniExcel { - public static async Task InsertAsync(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) + public static async Task InsertAsync(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) { if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") - throw new NotSupportedException("MiniExcel Insert not support xlsm"); + throw new NotSupportedException("MiniExcel's Insert does not support the .xlsm format"); if (!File.Exists(path)) { - await SaveAsAsync(path, value, printHeader, sheetName, excelType, cancellationToken: cancellationToken); + var rowsWritten = await SaveAsAsync(path, value, printHeader, sheetName, excelType, configuration, cancellationToken: cancellationToken); + return rowsWritten.FirstOrDefault(); + } + + if (excelType == ExcelType.CSV) + { + using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan)) + return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken); } else { - if (excelType == ExcelType.CSV) - { - using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan)) - await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken); - } - else - { - using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan)) - await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken); - } + using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan)) + return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken); } } - public static async Task InsertAsync(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) + public static async TaskInsertAsync(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) { stream.Seek(0, SeekOrigin.End); // reuse code if (excelType == ExcelType.CSV) { - object v = null; - { - if (!(value is IEnumerable) && !(value is IDataReader) && !(value is IDictionary) && !(value is IDictionary)) - v = Enumerable.Range(0, 1).Select(s => value); - else - v = value; - } - await ExcelWriterFactory.GetProvider(stream, v, sheetName, excelType, configuration, false).InsertAsync(overwriteSheet); + var newValue = value is IEnumerable || value is IDataReader ? value : new[]{value}.AsEnumerable(); + return await ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false).InsertAsync(overwriteSheet); } else { - if (configuration == null) - { - configuration = new OpenXmlConfiguration { FastMode = true }; - } - await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).InsertAsync(overwriteSheet); + var configOrDefault = configuration ?? new OpenXmlConfiguration { FastMode = true }; + return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configOrDefault, printHeader).InsertAsync(overwriteSheet); } } - public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default(CancellationToken)) { if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") - throw new NotSupportedException("MiniExcel SaveAs not support xlsm"); + throw new NotSupportedException("MiniExcel's SaveAs does not support the .xlsm format"); using (var stream = overwriteFile ? File.Create(path) : new FileStream(path, FileMode.CreateNew)) - await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration); + return await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration); } - public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) { - await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAsAsync(cancellationToken); + return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAsAsync(cancellationToken); } public static async Task MergeSameCellsAsync(string mergedFilePath, string path, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) @@ -108,7 +98,7 @@ public static async Task InsertAsync(this Stream stream, object value, string sh public static async Task> QueryAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) { - TaskCompletionSource> tcs = new TaskCompletionSource>(); + var tcs = new TaskCompletionSource>(); cancellationToken.Register(() => { tcs.TrySetCanceled(); diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index 62ab3013..818026b2 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -24,67 +24,57 @@ public static MiniExcelDataReader GetReader(this Stream stream, bool useHeaderRo return new MiniExcelDataReader(stream, useHeaderRow, sheetName, excelType, startCell, configuration); } - public static void Insert(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false) + public static int Insert(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false) { if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") - throw new NotSupportedException("MiniExcel Insert not support xlsm"); + throw new NotSupportedException("MiniExcel's Insert does not support the .xlsm format"); if (!File.Exists(path)) { - SaveAs(path, value, printHeader, sheetName, excelType, configuration); + var rowsWritten = SaveAs(path, value, printHeader, sheetName, excelType, configuration); + return rowsWritten.FirstOrDefault(); + } + + if (excelType == ExcelType.CSV) + { + using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan)) + return Insert(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet); } else { - if (excelType == ExcelType.CSV) - { - using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan)) - Insert(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet); - } - else - { - using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan)) - Insert(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet); - } + using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan)) + return Insert(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet); } } - public static void Insert(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false) + public static int Insert(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false) { stream.Seek(0, SeekOrigin.End); // reuse code if (excelType == ExcelType.CSV) { - object v = null; - { - if (!(value is IEnumerable) && !(value is IDataReader)) - v = Enumerable.Range(0, 1).Select(s => value); - else - v = value; - } - ExcelWriterFactory.GetProvider(stream, v, sheetName, excelType, configuration, false).Insert(overwriteSheet); + var newValue = value is IEnumerable || value is IDataReader ? value : new[]{value}.AsEnumerable(); + return ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false).Insert(overwriteSheet); } else { - if (configuration == null) - { - configuration = new OpenXmlConfiguration { FastMode = true }; - } - ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).Insert(overwriteSheet); - } + var configOrDefault = configuration ?? new OpenXmlConfiguration { FastMode = true }; + return ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configOrDefault, printHeader).Insert(overwriteSheet); + } } - public static void SaveAs(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false) + public static int[] SaveAs(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false) { if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") - throw new NotSupportedException("MiniExcel SaveAs not support xlsm"); + throw new NotSupportedException("MiniExcel's SaveAs does not support the .xlsm format"); using (var stream = overwriteFile ? File.Create(path) : new FileStream(path, FileMode.CreateNew)) - SaveAs(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration); + return SaveAs(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration); } - public static void SaveAs(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null) + public static int[] SaveAs(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null) { - ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAs(); + return ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAs(); } public static IEnumerable Query(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new() diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index 88ab2411..76f2bed7 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -17,83 +17,97 @@ namespace MiniExcelLibs.OpenXml { internal partial class ExcelOpenXmlSheetWriter : IExcelWriter { - public async Task SaveAsAsync(CancellationToken cancellationToken = default) + public async Task SaveAsAsync(CancellationToken cancellationToken = default) { - await GenerateDefaultOpenXmlAsync(cancellationToken); + try + { + await GenerateDefaultOpenXmlAsync(cancellationToken); - var sheets = GetSheets(); + var sheets = GetSheets(); + var rowsWritten = new List(); - foreach (var sheet in sheets) + foreach (var sheet in sheets) + { + _sheets.Add(sheet.Item1); //TODO:remove + currentSheetIndex = sheet.Item1.SheetIdx; + var rows = await CreateSheetXmlAsync(sheet.Item2, sheet.Item1.Path, cancellationToken); + rowsWritten.Add(rows); + } + + await GenerateEndXmlAsync(cancellationToken); + return rowsWritten.ToArray(); + } + finally { - _sheets.Add(sheet.Item1); //TODO:remove - currentSheetIndex = sheet.Item1.SheetIdx; - await CreateSheetXmlAsync(sheet.Item2, sheet.Item1.Path, cancellationToken); + _archive.Dispose(); } - - await GenerateEndXmlAsync(cancellationToken); - _archive.Dispose(); } - public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) + public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) { - if (!_configuration.FastMode) + try { - throw new InvalidOperationException("Insert requires fast mode to be enabled"); - } + if (!_configuration.FastMode) + throw new InvalidOperationException("Insert requires fast mode to be enabled"); - var sheetRecords = new ExcelOpenXmlSheetReader(_stream, _configuration).GetWorkbookRels(_archive.Entries).ToArray(); - foreach (var sheetRecord in sheetRecords.OrderBy(o => o.Id)) - { - _sheets.Add(new SheetDto { Name = sheetRecord.Name, SheetIdx = (int)sheetRecord.Id, State = sheetRecord.State }); - } - var existSheetDto = _sheets.SingleOrDefault(s => s.Name == _defaultSheetName); - if (existSheetDto != null && !overwriteSheet) - { - throw new Exception($"Sheet “{_defaultSheetName}” already exist"); - } + { + cancellationToken.ThrowIfCancellationRequested(); + _sheets.Add(new SheetDto { Name = sheetRecord.Name, SheetIdx = (int)sheetRecord.Id, State = sheetRecord.State }); + } + var existSheetDto = _sheets.SingleOrDefault(s => s.Name == _defaultSheetName); + if (existSheetDto != null && !overwriteSheet) + throw new Exception($"Sheet “{_defaultSheetName}” already exist"); - await GenerateStylesXmlAsync(cancellationToken);//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 + await GenerateStylesXmlAsync(cancellationToken);//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 if (existSheetDto == null) - { - currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; var insertSheetInfo = GetSheetInfos(_defaultSheetName); - var insertSheetDto = insertSheetInfo.ToDto(currentSheetIndex); - _sheets.Add(insertSheetDto); - await CreateSheetXmlAsync(_value, insertSheetDto.Path, cancellationToken); - } - else - { - currentSheetIndex = existSheetDto.SheetIdx; - _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); - await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken); - } + int rowsWritten; + if (existSheetDto == null) + { + currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; + var insertSheetInfo = GetSheetInfos(_defaultSheetName); + var insertSheetDto = insertSheetInfo.ToDto(currentSheetIndex); + _sheets.Add(insertSheetDto); + rowsWritten = await CreateSheetXmlAsync(_value, insertSheetDto.Path, cancellationToken); + } + else + { + currentSheetIndex = existSheetDto.SheetIdx; + _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); + rowsWritten = await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken); + } - await AddFilesToZipAsync(cancellationToken); + await AddFilesToZipAsync(cancellationToken); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex - 1))?.Delete(); - await GenerateDrawinRelXmlAsync(currentSheetIndex - 1, cancellationToken); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex - 1))?.Delete(); + await GenerateDrawinRelXmlAsync(currentSheetIndex - 1, cancellationToken); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex - 1))?.Delete(); - await GenerateDrawingXmlAsync(currentSheetIndex - 1, cancellationToken); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex - 1))?.Delete(); + await GenerateDrawingXmlAsync(currentSheetIndex - 1, cancellationToken); - GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary sheetsRelsXml); - foreach (var sheetRelsXml in sheetsRelsXml) - { - var sheetRelsXmlPath = ExcelFileNames.SheetRels(sheetRelsXml.Key); - _archive.Entries.SingleOrDefault(s => s.FullName == sheetRelsXmlPath)?.Delete(); - await CreateZipEntryAsync(sheetRelsXmlPath, null, ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), cancellationToken); - } + GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary sheetsRelsXml); + foreach (var sheetRelsXml in sheetsRelsXml) + { + var sheetRelsXmlPath = ExcelFileNames.SheetRels(sheetRelsXml.Key); + _archive.Entries.SingleOrDefault(s => s.FullName == sheetRelsXmlPath)?.Delete(); + await CreateZipEntryAsync(sheetRelsXmlPath, null, ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), cancellationToken); + } - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete(); - await CreateZipEntryAsync(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), cancellationToken); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete(); + await CreateZipEntryAsync(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), cancellationToken); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); - await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); + await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken); - await InsertContentTypesXmlAsync(cancellationToken); + await InsertContentTypesXmlAsync(cancellationToken); - _archive.Dispose(); + return rowsWritten; + } + finally + { + _archive.Dispose(); + } } internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken) @@ -103,11 +117,15 @@ internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationTo await GenerateStylesXmlAsync(cancellationToken); } - private async Task CreateSheetXmlAsync(object values, string sheetPath, CancellationToken cancellationToken) + private async Task CreateSheetXmlAsync(object values, string sheetPath, CancellationToken cancellationToken) { - ZipArchiveEntry entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); + cancellationToken.ThrowIfCancellationRequested(); + + var entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); + var rowsWritten = 0; + using (var zipStream = entry.Open()) - using (MiniExcelAsyncStreamWriter writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken)) + using (var writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken)) { if (values == null) { @@ -115,18 +133,19 @@ private async Task CreateSheetXmlAsync(object values, string sheetPath, Cancella } else { - await WriteValuesAsync(writer, values, cancellationToken); + rowsWritten = await WriteValuesAsync(writer, values, cancellationToken); } } _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); + return rowsWritten; } - private async Task WriteEmptySheetAsync(MiniExcelAsyncStreamWriter writer) + private static async Task WriteEmptySheetAsync(MiniExcelAsyncStreamWriter writer) { await writer.WriteAsync(ExcelXml.EmptySheetXml); } - private async Task WriteDimensionPlaceholderAsync(MiniExcelAsyncStreamWriter writer) + private static async Task WriteDimensionPlaceholderAsync(MiniExcelAsyncStreamWriter writer) { var dimensionPlaceholderPostition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension); await writer.WriteAsync(WorksheetXml.DimensionPlaceholder); // end of code will be replaced @@ -134,7 +153,7 @@ private async Task WriteDimensionPlaceholderAsync(MiniExcelAsyncStreamWrit return dimensionPlaceholderPostition; } - private async Task WriteDimensionAsync(MiniExcelAsyncStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) + private static async Task WriteDimensionAsync(MiniExcelAsyncStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) { // Flush and save position so that we can get back again. var position = await writer.FlushAsync(); @@ -145,7 +164,7 @@ private async Task WriteDimensionAsync(MiniExcelAsyncStreamWriter writer, int ma writer.SetPosition(position); } - private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object values, CancellationToken cancellationToken) + private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object values, CancellationToken cancellationToken) { #if NETSTANDARD2_0_OR_GREATER || NET IMiniExcelWriteAdapter writeAdapter = null; @@ -167,7 +186,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object va if (props == null) { await WriteEmptySheetAsync(writer); - return; + return 0; } var maxColumnIndex = props.Count; int maxRowIndex; @@ -251,7 +270,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object va await writer.WriteAsync(WorksheetXml.EndWorksheet); - if (_configuration.FastMode && dimensionPlaceholderPostition != default) + if (_configuration.FastMode && dimensionPlaceholderPostition != 0) { await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); } @@ -291,10 +310,10 @@ private async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IE } await writer.WriteAsync(WorksheetXml.Column(column.Index, column.Width)); } + if (!hasWrittenStart) - { return; - } + await writer.WriteAsync(WorksheetXml.EndCols); } @@ -366,13 +385,9 @@ private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowInde private async Task GenerateEndXmlAsync(CancellationToken cancellationToken) { await AddFilesToZipAsync(cancellationToken); - await GenerateDrawinRelXmlAsync(cancellationToken); - await GenerateDrawingXmlAsync(cancellationToken); - await GenerateWorkbookXmlAsync(cancellationToken); - await GenerateContentTypesXmlAsync(cancellationToken); } @@ -388,7 +403,7 @@ private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) { using (var context = new SheetStyleBuildContext(_zipDictionary, _archive, _utf8WithBom, _configuration.DynamicColumns)) { - var builder = (ISheetStyleBuilder)null; + ISheetStyleBuilder builder = null; switch (_configuration.TableStyles) { case TableStyles.None: @@ -399,7 +414,7 @@ private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) break; } var result = await builder.BuildAsync(cancellationToken); - cellXfIdMap = result.CellXfIdMap; + _cellXfIdMap = result.CellXfIdMap; } } @@ -483,16 +498,19 @@ private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToke await GenerateContentTypesXmlAsync(cancellationToken); return; } + using (var stream = contentTypesZipEntry.Open()) { var doc = XDocument.Load(stream); var ns = doc.Root.GetDefaultNamespace(); var typesElement = doc.Descendants(ns + "Types").Single(); + var partNames = new HashSet(StringComparer.InvariantCultureIgnoreCase); foreach (var partName in typesElement.Elements(ns + "Override").Select(s => s.Attribute("PartName").Value)) { partNames.Add(partName); } + foreach (var p in _zipDictionary) { var partName = $"/{p.Key}"; @@ -502,6 +520,7 @@ private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToke typesElement.Add(newElement); } } + stream.Position = 0; doc.Save(stream); } @@ -510,9 +529,11 @@ private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToke private async Task CreateZipEntryAsync(string path, string contentType, string content, CancellationToken cancellationToken) { ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); + var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); using (var zipStream = entry.Open()) - using (MiniExcelAsyncStreamWriter writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken)) + using (var writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken)) await writer.WriteAsync(content); + if (!string.IsNullOrEmpty(contentType)) _zipDictionary.Add(path, new ZipPackageInfo(entry, contentType)); } @@ -520,6 +541,7 @@ private async Task CreateZipEntryAsync(string path, string contentType, string c private async Task CreateZipEntryAsync(string path, byte[] content, CancellationToken cancellationToken) { ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); + var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); using (var zipStream = entry.Open()) await zipStream.WriteAsync(content, 0, content.Length, cancellationToken); } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index 2c5639ee..db031596 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -17,7 +17,7 @@ namespace MiniExcelLibs.OpenXml internal partial class ExcelOpenXmlSheetWriter : IExcelWriter { private readonly Dictionary _zipDictionary = new Dictionary(); - private Dictionary cellXfIdMap; + private Dictionary _cellXfIdMap; private IEnumerable> GetSheets() { @@ -61,21 +61,15 @@ private ExcellSheetInfo GetSheetInfos(string sheetName) }; if (_configuration.DynamicSheets == null || _configuration.DynamicSheets.Length <= 0) - { return info; - } var dynamicSheet = _configuration.DynamicSheets.SingleOrDefault(_ => _.Key == sheetName); if (dynamicSheet == null) - { return info; - } info.ExcelSheetState = dynamicSheet.State; - if (dynamicSheet.Name != null) - { + if (dynamicSheet.Name != null) info.ExcelSheetName = dynamicSheet.Name; - } return info; } @@ -84,9 +78,7 @@ private string GetSheetViews() { // exit early if no style to write if (_configuration.FreezeRowCount <= 0 && _configuration.FreezeColumnCount <= 0) - { return string.Empty; - } var sb = new StringBuilder(); @@ -106,21 +98,20 @@ private string GetSheetViews() private string GetPanes() { - var sb = new StringBuilder(); string activePane; - if (_configuration.FreezeColumnCount > 0 && _configuration.FreezeRowCount > 0) - { - activePane = "bottomRight"; - } - else if (_configuration.FreezeColumnCount > 0) - { - activePane = "topRight"; - } - else - { - activePane = "bottomLeft"; + switch (_configuration.FreezeColumnCount > 0) + { + case true when _configuration.FreezeRowCount > 0: + activePane = "bottomRight"; + break; + case true: + activePane = "topRight"; + break; + default: + activePane = "bottomLeft"; + break; } sb.Append( WorksheetXml.StartPane( @@ -174,24 +165,18 @@ private string GetPanes() } return sb.ToString(); - } private Tuple GetCellValue(int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, bool valueIsNull) { if (valueIsNull) - { return Tuple.Create("2", "str", string.Empty); - } if (value is string str) - { return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(str)); - } var type = GetValueType(value, columnInfo); - if (columnInfo?.ExcelFormat != null && columnInfo?.ExcelFormatId == -1 && value is IFormattable formattableValue) { var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture); @@ -199,15 +184,11 @@ private Tuple GetCellValue(int rowIndex, int cellIndex, } if (type == typeof(DateTime)) - { return GetDateTimeValue((DateTime)value, columnInfo); - } #if NET6_0_OR_GREATER if (type == typeof(DateOnly)) - { return GetDateTimeValue(((DateOnly)value).ToDateTime(new TimeOnly()), columnInfo); - } #endif if (type.IsEnum) { @@ -224,16 +205,12 @@ private Tuple GetCellValue(int rowIndex, int cellIndex, var dataType = _configuration.Culture == CultureInfo.InvariantCulture ? "n" : "str"; return Tuple.Create("2", dataType, cellValue); } - else - { - return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue); - } + + return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue); } if (type == typeof(bool)) - { return Tuple.Create("2", "b", (bool)value ? "1" : "0"); - } if (type == typeof(byte[]) && _configuration.EnableConvertByteArray) { @@ -265,49 +242,31 @@ private static Type GetValueType(object value, ExcelColumnInfo columnInfo) private string GetNumericValue(object value, Type type) { if (type.IsAssignableFrom(typeof(decimal))) - { return ((decimal)value).ToString(_configuration.Culture); - } if (type.IsAssignableFrom(typeof(int))) - { return ((int)value).ToString(_configuration.Culture); - } if (type.IsAssignableFrom(typeof(double))) - { return ((double)value).ToString(_configuration.Culture); - } if (type.IsAssignableFrom(typeof(long))) - { return ((long)value).ToString(_configuration.Culture); - } if (type.IsAssignableFrom(typeof(uint))) - { return ((uint)value).ToString(_configuration.Culture); - } if (type.IsAssignableFrom(typeof(ushort))) - { return ((ushort)value).ToString(_configuration.Culture); - } if (type.IsAssignableFrom(typeof(ulong))) - { return ((ulong)value).ToString(_configuration.Culture); - } if (type.IsAssignableFrom(typeof(short))) - { return ((short)value).ToString(_configuration.Culture); - } if (type.IsAssignableFrom(typeof(float))) - { return ((float)value).ToString(_configuration.Culture); - } return (decimal.Parse(value.ToString())).ToString(_configuration.Culture); } @@ -349,18 +308,19 @@ private string GetFileValue(int rowIndex, int cellIndex, object value) private Tuple GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo) { string cellValue = null; - if (_configuration.Culture != CultureInfo.InvariantCulture) + if (!_configuration.Culture.Equals(CultureInfo.InvariantCulture)) { - cellValue = (value).ToString(_configuration.Culture); + cellValue = value.ToString(_configuration.Culture); return Tuple.Create("2", "str", cellValue); } var oaDate = CorrectDateTimeValue(value); cellValue = oaDate.ToString(CultureInfo.InvariantCulture); - if (columnInfo == null || columnInfo.ExcelFormat == null) - return Tuple.Create("3", null, cellValue); - else + + if (columnInfo?.ExcelFormat != null) return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue); + + return Tuple.Create("3", (string)null, cellValue); } private static double CorrectDateTimeValue(DateTime value) @@ -379,7 +339,7 @@ private static double CorrectDateTimeValue(DateTime value) return oaDate; } - private string GetDimensionRef(int maxRowIndex, int maxColumnIndex) + private static string GetDimensionRef(int maxRowIndex, int maxColumnIndex) { string dimensionRef; if (maxRowIndex == 0 && maxColumnIndex == 0) @@ -456,11 +416,7 @@ private string GetContentTypesXml() private string GetCellXfId(string styleIndex) { - if (cellXfIdMap.TryGetValue(styleIndex, out var cellXfId)) - { - return cellXfId.ToString(); - } - return styleIndex.ToString(); + return _cellXfIdMap.TryGetValue(styleIndex, out var cellXfId) ? cellXfId : styleIndex; } } } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 39526cf6..66e864d8 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -30,46 +30,47 @@ internal partial class ExcelOpenXmlSheetWriter : IExcelWriter public ExcelOpenXmlSheetWriter(Stream stream, object value, string sheetName, IConfiguration configuration, bool printHeader) { - this._stream = stream; + _stream = stream; // Why ZipArchiveMode.Update not ZipArchiveMode.Create? // R : Mode create - ZipArchiveEntry does not support seeking.' - this._configuration = configuration as OpenXmlConfiguration ?? OpenXmlConfiguration.DefaultConfig; + _configuration = configuration as OpenXmlConfiguration ?? OpenXmlConfiguration.DefaultConfig; if (_configuration.EnableAutoWidth && !_configuration.FastMode) - { throw new InvalidOperationException("Auto width requires fast mode to be enabled"); - } - if (_configuration.FastMode) - this._archive = new MiniExcelZipArchive(_stream, ZipArchiveMode.Update, true, _utf8WithBom); - else - this._archive = new MiniExcelZipArchive(_stream, ZipArchiveMode.Create, true, _utf8WithBom); - this._printHeader = printHeader; - this._value = value; - this._defaultSheetName = sheetName; + var archiveMode = _configuration.FastMode ? ZipArchiveMode.Update : ZipArchiveMode.Create; + _archive = new MiniExcelZipArchive(_stream, archiveMode, true, _utf8WithBom); + + _value = value; + _printHeader = printHeader; + _defaultSheetName = sheetName; } public ExcelOpenXmlSheetWriter() { } - public void SaveAs() + public int[] SaveAs() { GenerateDefaultOpenXml(); var sheets = GetSheets(); - + var rowsWritten = new List(); + foreach (var sheet in sheets) { _sheets.Add(sheet.Item1); //TODO:remove currentSheetIndex = sheet.Item1.SheetIdx; - CreateSheetXml(sheet.Item2, sheet.Item1.Path); + var rows = CreateSheetXml(sheet.Item2, sheet.Item1.Path); + rowsWritten.Add(rows); } GenerateEndXml(); _archive.Dispose(); + + return rowsWritten.ToArray(); } - public void Insert(bool overwriteSheet = false) + public int Insert(bool overwriteSheet = false) { if (!_configuration.FastMode) { @@ -88,20 +89,20 @@ public void Insert(bool overwriteSheet = false) } GenerateStylesXml();//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 - + int rowsWritten; if (existSheetDto == null) { currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; var insertSheetInfo = GetSheetInfos(_defaultSheetName); var insertSheetDto = insertSheetInfo.ToDto(currentSheetIndex); _sheets.Add(insertSheetDto); - CreateSheetXml(_value, insertSheetDto.Path); + rowsWritten = CreateSheetXml(_value, insertSheetDto.Path); } else { currentSheetIndex = existSheetDto.SheetIdx; _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); - CreateSheetXml(_value, existSheetDto.Path); + rowsWritten = CreateSheetXml(_value, existSheetDto.Path); } AddFilesToZip(); @@ -129,6 +130,8 @@ public void Insert(bool overwriteSheet = false) InsertContentTypesXml(); _archive.Dispose(); + + return rowsWritten; } internal void GenerateDefaultOpenXml() @@ -138,11 +141,13 @@ internal void GenerateDefaultOpenXml() GenerateStylesXml(); } - private void CreateSheetXml(object values, string sheetPath) + private int CreateSheetXml(object values, string sheetPath) { - ZipArchiveEntry entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); + var entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); + var rowsWritten = 0; + using (var zipStream = entry.Open()) - using (MiniExcelStreamWriter writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) + using (var writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) { if (values == null) { @@ -150,18 +155,19 @@ private void CreateSheetXml(object values, string sheetPath) } else { - WriteValues(writer, values); + rowsWritten = WriteValues(writer, values); } } _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); + return rowsWritten; } - private void WriteEmptySheet(MiniExcelStreamWriter writer) + private static void WriteEmptySheet(MiniExcelStreamWriter writer) { writer.Write(ExcelXml.EmptySheetXml); } - private long WriteDimensionPlaceholder(MiniExcelStreamWriter writer) + private static long WriteDimensionPlaceholder(MiniExcelStreamWriter writer) { var dimensionPlaceholderPostition = writer.WriteAndFlush(WorksheetXml.StartDimension); writer.Write(WorksheetXml.DimensionPlaceholder); // end of code will be replaced @@ -180,16 +186,16 @@ private void WriteDimension(MiniExcelStreamWriter writer, int maxRowIndex, int m writer.SetPosition(position); } - private void WriteValues(MiniExcelStreamWriter writer, object values) + private int WriteValues(MiniExcelStreamWriter writer, object values) { - IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + var writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); var isKnownCount = writeAdapter.TryGetKnownCount(out var count); var props = writeAdapter.GetColumns(); if (props == null) { WriteEmptySheet(writer); - return; + return 0; } var maxColumnIndex = props.Count; int maxRowIndex; @@ -255,7 +261,7 @@ private void WriteValues(MiniExcelStreamWriter writer, object values) writer.Write(WorksheetXml.Drawing(currentSheetIndex)); writer.Write(WorksheetXml.EndWorksheet); - if (_configuration.FastMode && dimensionPlaceholderPostition != default) + if (_configuration.FastMode && dimensionPlaceholderPostition != 0) { WriteDimension(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); } @@ -263,16 +269,19 @@ private void WriteValues(MiniExcelStreamWriter writer, object values) { OverWriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths.Columns); } + + var toSubtract = _printHeader ? 1 : 0; + return maxRowIndex - toSubtract; } - private long WriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, ICollection props) + private static long WriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, ICollection props) { var placeholderPosition = writer.Flush(); writer.WriteWhitespace(WorksheetXml.GetColumnPlaceholderLength(props.Count)); return placeholderPosition; } - private void OverWriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, long placeholderPosition, IEnumerable columnWidths) + private static void OverWriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, long placeholderPosition, IEnumerable columnWidths) { var position = writer.Flush(); @@ -283,7 +292,7 @@ private void OverWriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, long writer.SetPosition(position); } - private void WriteColumnsWidths(MiniExcelStreamWriter writer, IEnumerable columnWidths) + private static void WriteColumnsWidths(MiniExcelStreamWriter writer, IEnumerable columnWidths) { var hasWrittenStart = false; foreach (var column in columnWidths) @@ -400,7 +409,7 @@ private void GenerateStylesXml() break; } var result = builder.Build(); - cellXfIdMap = result.CellXfIdMap; + _cellXfIdMap = result.CellXfIdMap; } } diff --git a/src/MiniExcel/OpenXml/MiniExcelAsyncStreamWriter.cs b/src/MiniExcel/OpenXml/MiniExcelAsyncStreamWriter.cs index 828830f1..b2f8aff1 100644 --- a/src/MiniExcel/OpenXml/MiniExcelAsyncStreamWriter.cs +++ b/src/MiniExcel/OpenXml/MiniExcelAsyncStreamWriter.cs @@ -16,27 +16,28 @@ internal class MiniExcelAsyncStreamWriter : IDisposable private readonly Encoding _encoding; private readonly CancellationToken _cancellationToken; private readonly StreamWriter _streamWriter; - private bool disposedValue; + private bool _disposedValue; + public MiniExcelAsyncStreamWriter(Stream stream, Encoding encoding, int bufferSize, CancellationToken cancellationToken) { - this._stream = stream; - this._encoding = encoding; - this._cancellationToken = cancellationToken; - this._streamWriter = new StreamWriter(stream, this._encoding, bufferSize); + _stream = stream; + _encoding = encoding; + _cancellationToken = cancellationToken; + _streamWriter = new StreamWriter(stream, _encoding, bufferSize); } public async Task WriteAsync(string content) { - this._cancellationToken.ThrowIfCancellationRequested(); + _cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrEmpty(content)) return; - await this._streamWriter.WriteAsync(content); + await _streamWriter.WriteAsync(content); } public async Task WriteAndFlushAsync(string content) { - await this.WriteAsync(content); - return await this.FlushAsync(); + await WriteAsync(content); + return await FlushAsync(); } public async Task WriteWhitespaceAsync(int length) @@ -46,23 +47,23 @@ public async Task WriteWhitespaceAsync(int length) public async Task FlushAsync() { - this._cancellationToken.ThrowIfCancellationRequested(); + _cancellationToken.ThrowIfCancellationRequested(); - await this._streamWriter.FlushAsync(); - return this._streamWriter.BaseStream.Position; + await _streamWriter.FlushAsync(); + return _streamWriter.BaseStream.Position; } public void SetPosition(long position) { - this._streamWriter.BaseStream.Position = position; + _streamWriter.BaseStream.Position = position; } protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { - this._streamWriter?.Dispose(); - disposedValue = true; + _streamWriter?.Dispose(); + _disposedValue = true; } } diff --git a/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs b/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs index 2114c11f..f9730a4f 100644 --- a/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs +++ b/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs @@ -32,16 +32,34 @@ public async Task Gb2312_Encoding_Read_Test() public async Task SeperatorTest() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); - var values = new List>() + var values = new List> + { + new Dictionary { - new Dictionary{{ "a", @"""<>+-*//}{\\n" }, { "b", 1234567890 },{ "c", true },{ "d", new DateTime(2021, 1, 1) } }, - new Dictionary{{ "a", @"Hello World" }, { "b", -1234567890 },{ "c", false },{ "d", new DateTime(2021, 1, 2) } }, - }; - await MiniExcel.SaveAsAsync(path, values, configuration: new Csv.CsvConfiguration() { Seperator = ';' }); - var expected = @"a;b;c;d -""""""<>+-*//}{\\n"";1234567890;True;""2021-01-01 00:00:00"" -""Hello World"";-1234567890;False;""2021-01-02 00:00:00"" -"; + { "a", @"""<>+-*//}{\\n" }, + { "b", 1234567890 }, + { "c", true }, + { "d", new DateTime(2021, 1, 1) } + }, + new Dictionary + { + { "a", "Hello World" }, + { "b", -1234567890 }, + { "c", false }, + { "d", new DateTime(2021, 1, 2) } + } + }; + + var rowsWritten = await MiniExcel.SaveAsAsync(path, values, configuration: new Csv.CsvConfiguration { Seperator = ';' }); + Assert.Equal(2, rowsWritten[0]); + + const string expected = + """" + a;b;c;d + """<>+-*//}{\\n";1234567890;True;"2021-01-01 00:00:00" + "Hello World";-1234567890;False;"2021-01-02 00:00:00" + + """"; Assert.Equal(expected, File.ReadAllText(path)); } @@ -65,26 +83,36 @@ public async Task SaveAsByDictionary() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); - var values = new List>() + var values = new List> { - new Dictionary{{ "a", @"""<>+-*//}{\\n" }, { "b", 1234567890 },{ "c", true },{ "d", new DateTime(2021, 1, 1) } }, - new Dictionary{{ "a", @"Hello World" }, { "b", -1234567890 },{ "c", false },{ "d", new DateTime(2021, 1, 2) } }, + new Dictionary + { + { "a", @"""<>+-*//}{\\n" }, { "b", 1234567890 }, { "c", true }, + { "d", new DateTime(2021, 1, 1) } + }, + + new Dictionary + { + { "a", "Hello World" }, { "b", -1234567890 }, { "c", false }, + { "d", new DateTime(2021, 1, 2) } + }, }; - await MiniExcel.SaveAsAsync(path, values); + var rowsWritten = await MiniExcel.SaveAsAsync(path, values); + Assert.Equal(2, rowsWritten[0]); using (var reader = new StreamReader(path)) using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csv.GetRecords().ToList(); Assert.Equal(@"""<>+-*//}{\\n", records[0].a); - Assert.Equal(@"1234567890", records[0].b); - Assert.Equal(@"True", records[0].c); - Assert.Equal(@"2021-01-01 00:00:00", records[0].d); - - Assert.Equal(@"Hello World", records[1].a); - Assert.Equal(@"-1234567890", records[1].b); - Assert.Equal(@"False", records[1].c); - Assert.Equal(@"2021-01-02 00:00:00", records[1].d); + Assert.Equal("1234567890", records[0].b); + Assert.Equal("True", records[0].c); + Assert.Equal("2021-01-01 00:00:00", records[0].d); + + Assert.Equal("Hello World", records[1].a); + Assert.Equal("-1234567890", records[1].b); + Assert.Equal("False", records[1].c); + Assert.Equal("2021-01-02 00:00:00", records[1].d); } File.Delete(path); @@ -94,10 +122,23 @@ public async Task SaveAsByDictionary() var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); var values = new List>() { - new Dictionary{{ 1, @"""<>+-*//}{\\n" }, { 2, 1234567890 },{ 3, true },{ 4, new DateTime(2021, 1, 1) } }, - new Dictionary{{ 1, @"Hello World" }, { 2, -1234567890 },{ 3, false },{4, new DateTime(2021, 1, 2) } }, + new Dictionary + { + { 1, @"""<>+-*//}{\\n" }, + { 2, 1234567890 }, + { 3, true }, + { 4, new DateTime(2021, 1, 1) } + }, + new Dictionary + { + { 1, "Hello World" }, + { 2, -1234567890 }, + { 3, false }, + { 4, new DateTime(2021, 1, 2) } + }, }; - await MiniExcel.SaveAsAsync(path, values); + var rowsWritten = await MiniExcel.SaveAsAsync(path, values); + Assert.Equal(2, rowsWritten[0]); using (var reader = new StreamReader(path)) using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) @@ -106,16 +147,16 @@ public async Task SaveAsByDictionary() { var row = records[0] as IDictionary; Assert.Equal(@"""<>+-*//}{\\n", row["1"]); - Assert.Equal(@"1234567890", row["2"]); - Assert.Equal(@"True", row["3"]); - Assert.Equal(@"2021-01-01 00:00:00", row["4"]); + Assert.Equal("1234567890", row["2"]); + Assert.Equal("True", row["3"]); + Assert.Equal("2021-01-01 00:00:00", row["4"]); } { var row = records[1] as IDictionary; - Assert.Equal(@"Hello World", row["1"]); - Assert.Equal(@"-1234567890", row["2"]); - Assert.Equal(@"False", row["3"]); - Assert.Equal(@"2021-01-02 00:00:00", row["4"]); + Assert.Equal("Hello World", row["1"]); + Assert.Equal("-1234567890", row["2"]); + Assert.Equal("False", row["3"]); + Assert.Equal("2021-01-02 00:00:00", row["4"]); } } @@ -145,29 +186,29 @@ public async Task SaveAsByDataTableTest() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, new DateTime(2021, 1, 1)); - table.Rows.Add(@"Hello World", -1234567890, false, new DateTime(2021, 1, 2)); + table.Rows.Add("Hello World", -1234567890, false, new DateTime(2021, 1, 2)); } - await MiniExcel.SaveAsAsync(path, table); - + var rowsWritten = await MiniExcel.SaveAsAsync(path, table); + Assert.Equal(2, rowsWritten[0]); + using (var reader = new StreamReader(path)) using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csv.GetRecords().ToList(); Assert.Equal(@"""<>+-*//}{\\n", records[0].a); - Assert.Equal(@"1234567890", records[0].b); - Assert.Equal(@"True", records[0].c); - Assert.Equal(@"2021-01-01 00:00:00", records[0].d); - - Assert.Equal(@"Hello World", records[1].a); - Assert.Equal(@"-1234567890", records[1].b); - Assert.Equal(@"False", records[1].c); - Assert.Equal(@"2021-01-02 00:00:00", records[1].d); + Assert.Equal("1234567890", records[0].b); + Assert.Equal("True", records[0].c); + Assert.Equal("2021-01-01 00:00:00", records[0].d); + + Assert.Equal("Hello World", records[1].a); + Assert.Equal("-1234567890", records[1].b); + Assert.Equal("False", records[1].c); + Assert.Equal("2021-01-02 00:00:00", records[1].d); } File.Delete(path); } - } @@ -332,15 +373,15 @@ static async IAsyncEnumerable GetValues() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - await MiniExcel.SaveAsAsync(path, GetValues()); - - var results = MiniExcel.Query(path); - - Assert.True(results.Count() == 2); - Assert.True(results.First().c1 == "A1"); - Assert.True(results.First().c2 == "B1"); - Assert.True(results.Last().c1 == "A2"); - Assert.True(results.Last().c2 == "B2"); + var rowsWritten = await MiniExcel.SaveAsAsync(path, GetValues()); + Assert.Equal(2, rowsWritten[0]); + + var results = MiniExcel.Query(path).ToList(); + Assert.Equal(2, results.Count); + Assert.Equal("A1", results[0].c1); + Assert.Equal("B1", results[0].c2); + Assert.Equal("A2", results[1].c1); + Assert.Equal("B2", results[1].c2); File.Delete(path); } diff --git a/tests/MiniExcelTests/MiniExcelCsvTests.cs b/tests/MiniExcelTests/MiniExcelCsvTests.cs index 119efdb0..4f8bb905 100644 --- a/tests/MiniExcelTests/MiniExcelCsvTests.cs +++ b/tests/MiniExcelTests/MiniExcelCsvTests.cs @@ -33,15 +33,32 @@ public void SeperatorTest() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); var values = new List>() + { + new Dictionary { - new Dictionary{{ "a", @"""<>+-*//}{\\n" }, { "b", 1234567890 },{ "c", true },{ "d", new DateTime(2021, 1, 1) } }, - new Dictionary{{ "a", @"Hello World" }, { "b", -1234567890 },{ "c", false },{ "d", new DateTime(2021, 1, 2) } }, - }; - MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration() { Seperator = ';' }); - var expected = @"a;b;c;d -""""""<>+-*//}{\\n"";1234567890;True;""2021-01-01 00:00:00"" -""Hello World"";-1234567890;False;""2021-01-02 00:00:00"" -"; + { "a", @"""<>+-*//}{\\n" }, + { "b", 1234567890 }, + { "c", true }, + { "d", new DateTime(2021, 1, 1) } + }, + new Dictionary + { + { "a", "Hello World" }, + { "b", -1234567890 }, + { "c", false }, + { "d", new DateTime(2021, 1, 2) } + } + }; + var rowsWritten = MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration { Seperator = ';' }); + Assert.Equal(2, rowsWritten[0]); + + const string expected = + """" + a;b;c;d + """<>+-*//}{\\n";1234567890;True;"2021-01-01 00:00:00" + "Hello World";-1234567890;False;"2021-01-02 00:00:00" + + """"; Assert.Equal(expected, File.ReadAllText(path)); } @@ -67,10 +84,18 @@ public void QuoteSpecialCharacters() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); var values = new List>() + { + new Dictionary { - new Dictionary{{ "a", @"potato,banana" }, { "b", "text\ntest" },{ "c", "text\rpotato" },{ "d", new DateTime(2021, 1, 1) } }, - }; - MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration()); + { "a", "potato,banana" }, + { "b", "text\ntest" }, + { "c", "text\rpotato" }, + { "d", new DateTime(2021, 1, 1) } + }, + }; + var rowsWritten = MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration()); + Assert.Equal(1, rowsWritten[0]); + var expected = "a,b,c,d\r\n\"potato,banana\",\"text\ntest\",\"text\rpotato\",\"2021-01-01 00:00:00\"\r\n"; Assert.Equal(expected, File.ReadAllText(path)); } @@ -97,24 +122,37 @@ public void SaveAsByDictionary() var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); var values = new List>() { - new Dictionary{{ "a", @"""<>+-*//}{\\n" }, { "b", 1234567890 },{ "c", true },{ "d", new DateTime(2021, 1, 1) } }, - new Dictionary{{ "a", @"Hello World" }, { "b", -1234567890 },{ "c", false },{ "d", new DateTime(2021, 1, 2) } }, + new Dictionary + { + { "a", @"""<>+-*//}{\\n" }, + { "b", 1234567890 }, + { "c", true }, + { "d", new DateTime(2021, 1, 1) } + }, + new Dictionary + { + { "a", "Hello World" }, + { "b", -1234567890 }, + { "c", false }, + { "d", new DateTime(2021, 1, 2) } + } }; - MiniExcel.SaveAs(path, values); + var rowsWritten = MiniExcel.SaveAs(path, values); + Assert.Equal(2, rowsWritten[0]); using (var reader = new StreamReader(path)) using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csv.GetRecords().ToList(); Assert.Equal(@"""<>+-*//}{\\n", records[0].a); - Assert.Equal(@"1234567890", records[0].b); - Assert.Equal(@"True", records[0].c); - Assert.Equal(@"2021-01-01 00:00:00", records[0].d); - - Assert.Equal(@"Hello World", records[1].a); - Assert.Equal(@"-1234567890", records[1].b); - Assert.Equal(@"False", records[1].c); - Assert.Equal(@"2021-01-02 00:00:00", records[1].d); + Assert.Equal("1234567890", records[0].b); + Assert.Equal("True", records[0].c); + Assert.Equal("2021-01-01 00:00:00", records[0].d); + + Assert.Equal("Hello World", records[1].a); + Assert.Equal("-1234567890", records[1].b); + Assert.Equal("False", records[1].c); + Assert.Equal("2021-01-02 00:00:00", records[1].d); } File.Delete(path); @@ -175,24 +213,25 @@ public void SaveAsByDataTableTest() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, new DateTime(2021, 1, 1)); - table.Rows.Add(@"Hello World", -1234567890, false, new DateTime(2021, 1, 2)); + table.Rows.Add("Hello World", -1234567890, false, new DateTime(2021, 1, 2)); } - MiniExcel.SaveAs(path, table); + var rowsWritten = MiniExcel.SaveAs(path, table); + Assert.Equal(2, rowsWritten[0]); using (var reader = new StreamReader(path)) using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csv.GetRecords().ToList(); Assert.Equal(@"""<>+-*//}{\\n", records[0].a); - Assert.Equal(@"1234567890", records[0].b); - Assert.Equal(@"True", records[0].c); - Assert.Equal(@"2021-01-01 00:00:00", records[0].d); - - Assert.Equal(@"Hello World", records[1].a); - Assert.Equal(@"-1234567890", records[1].b); - Assert.Equal(@"False", records[1].c); - Assert.Equal(@"2021-01-02 00:00:00", records[1].d); + Assert.Equal("1234567890", records[0].b); + Assert.Equal("True", records[0].c); + Assert.Equal("2021-01-01 00:00:00", records[0].d); + + Assert.Equal("Hello World", records[1].a); + Assert.Equal("-1234567890", records[1].b); + Assert.Equal("False", records[1].c); + Assert.Equal("2021-01-02 00:00:00", records[1].d); } File.Delete(path); @@ -391,12 +430,13 @@ string Generate(string value) string MiniExcelGenerateCsv(string value) { - var records = Enumerable.Range(1, 1).Select((s, idx) => new { v1 = value, v2 = value }); + IEnumerable records = [new { v1 = value, v2 = value }]; var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.csv"); using (var stream = File.Create(path)) { - stream.SaveAs(records, excelType: ExcelType.CSV); + var rowsWritten = stream.SaveAs(records, excelType: ExcelType.CSV); + Assert.Equal(1, rowsWritten[0]); } var content = File.ReadAllText(path); diff --git a/tests/MiniExcelTests/MiniExcelIssue2024Tests.cs b/tests/MiniExcelTests/MiniExcelIssue2024Tests.cs index 32d238f8..035cd130 100644 --- a/tests/MiniExcelTests/MiniExcelIssue2024Tests.cs +++ b/tests/MiniExcelTests/MiniExcelIssue2024Tests.cs @@ -30,12 +30,14 @@ public void TestIssue627() { AutoFilter = false, DynamicColumns = - new[] { - new DynamicExcelColumn("long2") { Format = "@", Width = 25 } - } + [ + new DynamicExcelColumn("long2") { Format = "@", Width = 25 } + ] }; var value = new[] { new { long2 = "1550432695793487872" } }; - MiniExcel.SaveAs(path, value, configuration: config); + var rowsWritten = MiniExcel.SaveAs(path, value, configuration: config); + Assert.Single(rowsWritten); + Assert.Equal(1, rowsWritten[0]); } } } \ No newline at end of file diff --git a/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs b/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs index 23efd893..cbc7f318 100644 --- a/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs @@ -54,10 +54,18 @@ public async Task Issue255() //saveas { var path = PathHelper.GetTempPath(); - var value = new Issue255DTO[] { - new Issue255DTO { Time = new DateTime(2021, 01, 01), Time2 = new DateTime(2021, 01, 01) } + var value = new[] + { + new Issue255DTO + { + Time = new DateTime(2021, 01, 01), + Time2 = new DateTime(2021, 01, 01) + } }; - await MiniExcel.SaveAsAsync(path, value); + var rowsWritten = await MiniExcel.SaveAsAsync(path, value); + Assert.Single(rowsWritten); + Assert.Equal(1, rowsWritten[0]); + var q = await MiniExcel.QueryAsync(path); var rows = q.ToList(); Assert.Equal("2021", rows[1].A.ToString()); @@ -99,24 +107,30 @@ public async Task Issue253() var value = new[] { new { col1 = "世界你好" } }; var path = PathHelper.GetTempPath(extension: "csv"); await MiniExcel.SaveAsAsync(path, value); - var expected = @"col1 -世界你好 -"; + var expected = + """ + col1 + 世界你好 + + """; Assert.Equal(expected, File.ReadAllText(path)); } { - System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var value = new[] { new { col1 = "世界你好" } }; var path = PathHelper.GetTempPath(extension: "csv"); - var config = new Csv.CsvConfiguration() + var config = new Csv.CsvConfiguration { StreamWriterFunc = (stream) => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) }; await MiniExcel.SaveAsAsync(path, value, excelType: ExcelType.CSV, configuration: config); - var expected = @"col1 -������� -"; + var expected = + """ + col1 + ������� + + """; Assert.Equal(expected, File.ReadAllText(path)); } @@ -125,9 +139,12 @@ public async Task Issue253() var value = cn.ExecuteReader(@"select '世界你好' col1"); var path = PathHelper.GetTempPath(extension: "csv"); await MiniExcel.SaveAsAsync(path, value); - var expected = @"col1 -世界你好 -"; + var expected = + """ + col1 + 世界你好 + + """; Assert.Equal(expected, File.ReadAllText(path)); } } @@ -142,11 +159,17 @@ public async Task Issue251() { var reader = await cn.ExecuteReaderAsync(@"select '""<>+-*//}{\\n' a,1234567890 b union all select 'Hello World',-1234567890"); var path = PathHelper.GetTempPath(extension: "csv"); - await MiniExcel.SaveAsAsync(path, reader); - var expected = @"a,b -""""""<>+-*//}{\\n"",1234567890 -""Hello World"",-1234567890 -"; + var rowsWritten = await MiniExcel.SaveAsAsync(path, reader); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + + var expected = + """" + a,b + """<>+-*//}{\\n",1234567890 + "Hello World",-1234567890 + + """"; Assert.Equal(expected, File.ReadAllText(path)); } } @@ -186,7 +209,9 @@ public async Task Issue243() new { name ="Jack",Age=25,InDate=new DateTime(2021,01,03)}, new { name ="Henry",Age=36,InDate=new DateTime(2020,05,03)}, }; - await MiniExcel.SaveAsAsync(path, value); + var rowsWritten = await MiniExcel.SaveAsAsync(path, value); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); var q = await MiniExcel.QueryAsync(path); var rows = q.ToList(); @@ -213,15 +238,18 @@ public class Issue243Dto public async Task Issue241() { - var value = new Issue241Dto[] { - new Issue241Dto{ Name="Jack",InDate=new DateTime(2021,01,04)}, - new Issue241Dto{ Name="Henry",InDate=new DateTime(2020,04,05)}, - }; + Issue241Dto[] value = + [ + new Issue241Dto { Name="Jack",InDate=new DateTime(2021,01,04) }, + new Issue241Dto { Name="Henry",InDate=new DateTime(2020,04,05) } + ]; // csv { var path = PathHelper.GetTempPath("csv"); - MiniExcel.SaveAs(path, value); + var rowsWritten = MiniExcel.SaveAs(path, value); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); { var q = await MiniExcel.QueryAsync(path, true); @@ -241,7 +269,9 @@ public async Task Issue241() // xlsx { var path = PathHelper.GetTempPath(); - await MiniExcel.SaveAsAsync(path, value); + var rowsWritten = await MiniExcel.SaveAsAsync(path, value); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); { var q = await MiniExcel.QueryAsync(path, true); @@ -275,7 +305,8 @@ public async Task Issue132() { { var path = PathHelper.GetTempPath(); - var value = new[] { + var value = new[] + { new { name ="Jack",Age=25,InDate=new DateTime(2021,01,03)}, new { name ="Henry",Age=36,InDate=new DateTime(2020,05,03)}, }; @@ -285,26 +316,32 @@ public async Task Issue132() { var path = PathHelper.GetTempPath(); - var value = new[] { + var value = new[] + { new { name ="Jack",Age=25,InDate=new DateTime(2021,01,03)}, new { name ="Henry",Age=36,InDate=new DateTime(2020,05,03)}, }; - var config = new OpenXmlConfiguration() + var config = new OpenXmlConfiguration { TableStyles = TableStyles.None }; - await MiniExcel.SaveAsAsync(path, value, configuration: config); + var rowsWritten = await MiniExcel.SaveAsAsync(path, value, configuration: config); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); } { var path = PathHelper.GetTempPath(); var value = JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(new[] { + JsonConvert.SerializeObject(new[] + { new { name ="Jack",Age=25,InDate=new DateTime(2021,01,03)}, new { name ="Henry",Age=36,InDate=new DateTime(2020,05,03)}, }) ); - await MiniExcel.SaveAsAsync(path, value); + var rowsWritten = await MiniExcel.SaveAsAsync(path, value); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); } } @@ -316,7 +353,7 @@ public async Task Issue235() { var path = PathHelper.GetTempPath(); - DataSet sheets = new DataSet(); + var sheets = new DataSet(); var users = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { Name = "Jack", Age = 25 }, new { Name = "Mike", Age = 44 } })); users.TableName = "users"; var department = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } })); ; @@ -324,8 +361,9 @@ public async Task Issue235() sheets.Tables.Add(users); sheets.Tables.Add(department); - await MiniExcel.SaveAsAsync(path, sheets); - + var rowsWritten = await MiniExcel.SaveAsAsync(path, sheets); + Assert.Equal(2, rowsWritten.Length); + Assert.Equal(2, rowsWritten[0]); var sheetNames = MiniExcel.GetSheetNames(path); Assert.Equal("users", sheetNames[0]); @@ -399,7 +437,10 @@ public async Task Issue234() ["users"] = users, ["department"] = department }; - await MiniExcel.SaveAsAsync(path, sheets); + var rowsWritten = await MiniExcel.SaveAsAsync(path, sheets); + Assert.Equal(2, rowsWritten.Length); + Assert.Equal(2, rowsWritten[0]); + var sheetNames = MiniExcel.GetSheetNames(path); Assert.Equal("users", sheetNames[0]); @@ -469,7 +510,10 @@ public async Task Issue230() using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { var path = PathHelper.GetTempPath(); - await MiniExcel.SaveAsAsync(path, reader, printHeader: true); + var rowsWritten = await MiniExcel.SaveAsAsync(path, reader, printHeader: true); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + var q = await MiniExcel.QueryAsync(path, true); var rows = q.ToList(); Assert.Equal(1, rows[0].id); @@ -607,7 +651,9 @@ public async Task Issue223() new Dictionary(){{"A",Guid.NewGuid()},{"B","HelloWorld"}}, }; var path = PathHelper.GetTempPath(); - MiniExcel.SaveAs(path, value); + var rowsWritten = MiniExcel.SaveAs(path, value); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); var dt = await MiniExcel.QueryAsDataTableAsync(path); var columns = dt.Columns; @@ -693,9 +739,11 @@ public async Task Issue211() using (var connection = new SQLiteConnection(connectionString)) { - var reader = connection.ExecuteReader(@"select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); + var reader = connection.ExecuteReader("select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); - MiniExcel.SaveAs(path, reader); + var rowsWritten = MiniExcel.SaveAs(path, reader); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); var q = await MiniExcel.QueryAsync(path, true); var rows = q.ToList(); @@ -712,7 +760,9 @@ public async Task EmptyDataReaderIssue() var path = PathHelper.GetTempPath(); var reader = Substitute.For(); - MiniExcel.SaveAs(path, reader, overwriteFile: true); + var rowsWritten = MiniExcel.SaveAs(path, reader, overwriteFile: true); + Assert.Single(rowsWritten); + Assert.Equal(0, rowsWritten[0]); var q = await MiniExcel.QueryAsync(path, true); var rows = q.ToList(); @@ -833,10 +883,13 @@ public async Task Issue89() { //csv { - var text = @"State -OnDuty -Fired -Leave"; + var text = + """ + State + OnDuty + Fired + Leave + """; var stream = new MemoryStream(); var writer = new StreamWriter(stream); writer.Write(text); @@ -849,7 +902,10 @@ public async Task Issue89() Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); var outputPath = PathHelper.GetTempPath("xlsx"); - MiniExcel.SaveAs(outputPath, rows); + var rowsWritten = MiniExcel.SaveAs(outputPath, rows); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); + var q2 = await MiniExcel.QueryAsync(outputPath); var rows2 = q2.ToList(); Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); @@ -867,7 +923,11 @@ public async Task Issue89() Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); var outputPath = PathHelper.GetTempPath(); - MiniExcel.SaveAs(outputPath, rows); + var rowsWritten = MiniExcel.SaveAs(outputPath, rows); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); + + var q1 = await MiniExcel.QueryAsync(outputPath); var rows2 = q1.ToList(); Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); @@ -906,13 +966,12 @@ public async Task Issue217() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); MiniExcel.SaveAs(path, table); - + var q = await MiniExcel.QueryAsync(path); var rows = q.ToList(); Assert.Equal("Name", rows[0].B); Assert.Equal("Limit", rows[0].C); - File.Delete(path); } @@ -1265,9 +1324,12 @@ public async Task Issue142() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.csv"); MiniExcel.SaveAs(path, new Issue142VO[] { new Issue142VO { MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", MyProperty7 = "MyProperty7" } }); - var expected = @"MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 -MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 -"; + var expected = + """ + MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + + """; Assert.Equal(expected, File.ReadAllText(path)); { @@ -1406,12 +1468,14 @@ public async Task Issue157() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); Console.WriteLine("==== SaveAs by strongly type ===="); - var input = JsonConvert.DeserializeObject>("[{\"ID\":\"78de23d2-dcb6-bd3d-ec67-c112bbc322a2\",\"Name\":\"Wade\",\"BoD\":\"2020-09-27T00:00:00\",\"Age\":5019,\"VIP\":false,\"Points\":5019.12,\"IgnoredProperty\":null},{\"ID\":\"20d3bfce-27c3-ad3e-4f70-35c81c7e8e45\",\"Name\":\"Felix\",\"BoD\":\"2020-10-25T00:00:00\",\"Age\":7028,\"VIP\":true,\"Points\":7028.46,\"IgnoredProperty\":null},{\"ID\":\"52013bf0-9aeb-48e6-e5f5-e9500afb034f\",\"Name\":\"Phelan\",\"BoD\":\"2021-10-04T00:00:00\",\"Age\":3836,\"VIP\":true,\"Points\":3835.7,\"IgnoredProperty\":null},{\"ID\":\"3b97b87c-7afe-664f-1af5-6914d313ae25\",\"Name\":\"Samuel\",\"BoD\":\"2020-06-21T00:00:00\",\"Age\":9352,\"VIP\":false,\"Points\":9351.71,\"IgnoredProperty\":null},{\"ID\":\"9a989c43-d55f-5306-0d2f-0fbafae135bb\",\"Name\":\"Raymond\",\"BoD\":\"2021-07-12T00:00:00\",\"Age\":8210,\"VIP\":true,\"Points\":8209.76,\"IgnoredProperty\":null}]"); - await MiniExcel.SaveAsAsync(path, input); + var input = JsonConvert.DeserializeObject>("""[{"ID":"78de23d2-dcb6-bd3d-ec67-c112bbc322a2","Name":"Wade","BoD":"2020-09-27T00:00:00","Age":5019,"VIP":false,"Points":5019.12,"IgnoredProperty":null},{"ID":"20d3bfce-27c3-ad3e-4f70-35c81c7e8e45","Name":"Felix","BoD":"2020-10-25T00:00:00","Age":7028,"VIP":true,"Points":7028.46,"IgnoredProperty":null},{"ID":"52013bf0-9aeb-48e6-e5f5-e9500afb034f","Name":"Phelan","BoD":"2021-10-04T00:00:00","Age":3836,"VIP":true,"Points":3835.7,"IgnoredProperty":null},{"ID":"3b97b87c-7afe-664f-1af5-6914d313ae25","Name":"Samuel","BoD":"2020-06-21T00:00:00","Age":9352,"VIP":false,"Points":9351.71,"IgnoredProperty":null},{"ID":"9a989c43-d55f-5306-0d2f-0fbafae135bb","Name":"Raymond","BoD":"2021-07-12T00:00:00","Age":8210,"VIP":true,"Points":8209.76,"IgnoredProperty":null}]"""); + var rowsWritten = await MiniExcel.SaveAsAsync(path, input); + Assert.Single(rowsWritten); + Assert.Equal(5, rowsWritten[0]); var q = await MiniExcel.QueryAsync(path, sheetName: "Sheet1"); var rows = q.ToList(); - Assert.Equal(6, rows.Count()); + Assert.Equal(6, rows.Count); Assert.Equal("Sheet1", MiniExcel.GetSheetNames(path).First()); using (var p = new ExcelPackage(new FileInfo(path))) @@ -1422,7 +1486,7 @@ public async Task Issue157() } } { - var path = @"../../../../../samples/xlsx/TestIssue157.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue157.xlsx"; { var q = await MiniExcel.QueryAsync(path, sheetName: "Sheet1"); @@ -1451,7 +1515,6 @@ public async Task Issue157() Assert.Equal(1, rows[0].IgnoredProperty); } } - } /// diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 7cb626f8..f8651cf7 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -155,35 +155,46 @@ public void TestIssueI4X92G() }; MiniExcel.SaveAs(path, value); var content = File.ReadAllText(path); - Assert.Contains(@"ID,Name,InDate -1,Jack,""2021-01-03 00:00:00"" -2,Henry,""2020-05-03 00:00:00"" -", content); + Assert.Contains(""" + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + + """, content); } { var value = new { ID = 3, Name = "Mike", InDate = new DateTime(2021, 04, 23) }; - MiniExcel.Insert(path, value); + var rowsWritten = MiniExcel.Insert(path, value); + Assert.Equal(1, rowsWritten); + var content = File.ReadAllText(path); - Assert.Equal(@"ID,Name,InDate -1,Jack,""2021-01-03 00:00:00"" -2,Henry,""2020-05-03 00:00:00"" -3,Mike,""2021-04-23 00:00:00"" -", content); + Assert.Equal(""" + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + 3,Mike,"2021-04-23 00:00:00" + + """, content); } { - var value = new[] { - new { ID=4,Name ="Frank",InDate=new DateTime(2021,06,07)}, - new { ID=5,Name ="Gloria",InDate=new DateTime(2022,05,03)}, + var value = new[] + { + new { ID=4,Name ="Frank",InDate=new DateTime(2021,06,07)}, + new { ID=5,Name ="Gloria",InDate=new DateTime(2022,05,03)}, }; - MiniExcel.Insert(path, value); + var rowsWritten = MiniExcel.Insert(path, value); + Assert.Equal(2, rowsWritten); + var content = File.ReadAllText(path); - Assert.Equal(@"ID,Name,InDate -1,Jack,""2021-01-03 00:00:00"" -2,Henry,""2020-05-03 00:00:00"" -3,Mike,""2021-04-23 00:00:00"" -4,Frank,""2021-06-07 00:00:00"" -5,Gloria,""2022-05-03 00:00:00"" -", content); + Assert.Equal(""" + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + 3,Mike,"2021-04-23 00:00:00" + 4,Frank,"2021-06-07 00:00:00" + 5,Gloria,"2022-05-03 00:00:00" + + """, content); } } @@ -195,7 +206,8 @@ public void TestIssueI4X92G() public void TestIssue430() { var outputPath = PathHelper.GetTempFilePath(); - var value = new[] { + var value = new[] + { new TestIssue430Dto{ Date=DateTimeOffset.Parse("2021-01-31 10:03:00 +05:00")} }; MiniExcel.SaveAs(outputPath, value); diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetTests.cs index 75ac1c26..5b495031 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetTests.cs @@ -260,7 +260,10 @@ public void WriteHiddenSheetTest() }; string path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - MiniExcel.SaveAs(path, sheets, configuration: configuration); + var rowsWritten = MiniExcel.SaveAs(path, sheets, configuration: configuration); + Assert.Equal(2, rowsWritten.Length); + Assert.Equal(2, rowsWritten[0]); + var sheetInfos = MiniExcel.GetSheetInformations(path).ToList(); Assert.Collection(sheetInfos, diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index 74c8a5dd..8b4add0e 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -530,7 +530,7 @@ public void SaveAsFileWithDimensionByICollection() //List { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new List() + var values = new List { new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"}, new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"}, @@ -880,10 +880,9 @@ public void SaveAsByIEnumerableIDictionary() } } - [Fact()] + [Fact] public void SaveAsFrozenRowsAndColumnsTest() { - var config = new OpenXmlConfiguration { FreezeRowCount = 1, @@ -895,7 +894,8 @@ public void SaveAsFrozenRowsAndColumnsTest() var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); MiniExcel.SaveAs( path, - new[] { + new[] + { new { Column1 = "MiniExcel", Column2 = 1 }, new { Column1 = "Github", Column2 = 2} }, @@ -1088,7 +1088,7 @@ public void QueryByDictionaryStringAndObjectParameterTest() public void SQLiteInsertTest() { // Avoid SQL Insert Large Size Xlsx OOM - var path = @"../../../../../samples/xlsx/Test5x2.xlsx"; + var path = "../../../../../samples/xlsx/Test5x2.xlsx"; var tempSqlitePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); var connectionString = $"Data Source={tempSqlitePath};Version=3;"; @@ -1119,14 +1119,17 @@ public void SQLiteInsertTest() File.Delete(tempSqlitePath); } - [Fact()] + [Fact] public void SaveAsBasicCreateTest() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - MiniExcel.SaveAs(path, new[] { - new { Column1 = "MiniExcel", Column2 = 1 }, - new { Column1 = "Github", Column2 = 2} + var rowsWritten = MiniExcel.SaveAs(path, new[] + { + new { Column1 = "MiniExcel", Column2 = 1 }, + new { Column1 = "Github", Column2 = 2} }); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); using (var stream = File.OpenRead(path)) { @@ -1143,18 +1146,21 @@ public void SaveAsBasicCreateTest() File.Delete(path); } - [Fact()] + [Fact] public void SaveAsBasicStreamTest() { { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new[] { - new { Column1 = "MiniExcel", Column2 = 1 }, - new { Column1 = "Github", Column2 = 2} + var values = new[] + { + new { Column1 = "MiniExcel", Column2 = 1 }, + new { Column1 = "Github", Column2 = 2} }; using (var stream = new FileStream(path, FileMode.CreateNew)) { - stream.SaveAs(values); + var rowsWritten = stream.SaveAs(values); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); } using (var stream = File.OpenRead(path)) @@ -1165,22 +1171,25 @@ public void SaveAsBasicStreamTest() Assert.Equal(1, rows[0].Column2); Assert.Equal("Github", rows[1].Column1); Assert.Equal(2, rows[1].Column2); - }; + } File.Delete(path); } { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new[] { - new { Column1 = "MiniExcel", Column2 = 1 }, - new { Column1 = "Github", Column2 = 2} + var values = new[] + { + new { Column1 = "MiniExcel", Column2 = 1 }, + new { Column1 = "Github", Column2 = 2} }; using (var stream = new MemoryStream()) using (var fileStream = new FileStream(path, FileMode.Create)) { - stream.SaveAs(values); + var rowsWritten = stream.SaveAs(values); stream.Seek(0, SeekOrigin.Begin); stream.CopyTo(fileStream); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); } using (var stream = File.OpenRead(path)) @@ -1191,36 +1200,43 @@ public void SaveAsBasicStreamTest() Assert.Equal(1, rows[0].Column2); Assert.Equal("Github", rows[1].Column1); Assert.Equal(2, rows[1].Column2); - }; + } File.Delete(path); } } - [Fact()] + [Fact] public void SaveAsSpecialAndTypeCreateTest() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - MiniExcel.SaveAs(path, new[] { + var rowsWritten = MiniExcel.SaveAs(path, new[] + { new { a = @"""<>+-*//}{\\n", b = 1234567890,c = true,d=DateTime.Now }, new { a = "Hello World", b = -1234567890,c=false,d=DateTime.Now.Date} - }); + }); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + var info = new FileInfo(path); - Assert.True(info.FullName == path); File.Delete(path); } - [Fact()] + [Fact] public void SaveAsFileEpplusCanReadTest() { var now = DateTime.Now; var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - MiniExcel.SaveAs(path, new[] { - new { a = @"""<>+-*//}{\\n", b = 1234567890,c = true,d= now}, - new { a = "Hello World", b = -1234567890,c=false,d=now.Date} + var rowsWritten = MiniExcel.SaveAs(path, new[] + { + new { a = @"""<>+-*//}{\\n", b = 1234567890,c = true,d= now}, + new { a = "Hello World", b = -1234567890,c=false,d=now.Date} }); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + using (var p = new ExcelPackage(new FileInfo(path))) { var ws = p.Workbook.Worksheets.First(); @@ -1231,22 +1247,26 @@ public void SaveAsFileEpplusCanReadTest() Assert.True(ws.Cells["D1"].Value.ToString() == "d"); Assert.True(ws.Cells["A2"].Value.ToString() == @"""<>+-*//}{\\n"); - Assert.True(ws.Cells["B2"].Value.ToString() == @"1234567890"); + Assert.True(ws.Cells["B2"].Value.ToString() == "1234567890"); Assert.True(ws.Cells["C2"].Value.ToString() == true.ToString()); Assert.True(ws.Cells["D2"].Value.ToString() == now.ToString()); } File.Delete(path); } - [Fact()] + [Fact] public void SavaAsClosedXmlCanReadTest() { var now = DateTime.Now; var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - MiniExcel.SaveAs(path, new[] { - new { a = @"""<>+-*//}{\\n", b = 1234567890,c = true,d= now}, - new { a = "Hello World", b = -1234567890,c=false,d=now.Date} - }, sheetName: "R&D"); + var rowsWritten = MiniExcel.SaveAs(path, new[] + { + new { a = @"""<>+-*//}{\\n", b = 1234567890,c = true,d= now}, + new { a = "Hello World", b = -1234567890,c=false,d=now.Date} + }, sheetName: "R&D"); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + using (var workbook = new XLWorkbook(path)) { var ws = workbook.Worksheets.First(); @@ -1266,33 +1286,38 @@ public void SavaAsClosedXmlCanReadTest() File.Delete(path); } - [Fact()] + [Fact] public void ContentTypeUriContentTypeReadCheckTest() { var now = DateTime.Now; var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - MiniExcel.SaveAs(path, new[] { - new { a = @"""<>+-*//}{\\n", b = 1234567890,c = true,d= now}, - new { a = "Hello World", b = -1234567890,c=false,d=now.Date} - }); - using (Package zip = System.IO.Packaging.Package.Open(path, FileMode.Open)) + var rowsWritten = MiniExcel.SaveAs(path, new[] { - var allParts = zip.GetParts().Select(s => new { s.CompressionOption, s.ContentType, s.Uri, s.Package.GetType().Name }) - .ToDictionary(s => s.Uri.ToString(), s => s) - ; - Assert.True(allParts[@"/xl/styles.xml"].ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"); - Assert.True(allParts[@"/xl/workbook.xml"].ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"); - Assert.True(allParts[@"/xl/worksheets/sheet1.xml"].ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"); - Assert.True(allParts[@"/xl/_rels/workbook.xml.rels"].ContentType == "application/vnd.openxmlformats-package.relationships+xml"); - Assert.True(allParts[@"/_rels/.rels"].ContentType == "application/vnd.openxmlformats-package.relationships+xml"); + new { a = @"""<>+-*//}{\\n", b = 1234567890,c = true,d= now}, + new { a = "Hello World", b = -1234567890,c=false,d=now.Date} + }); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + + using (var zip = Package.Open(path, FileMode.Open)) + { + var allParts = zip.GetParts() + .Select(s => new { s.CompressionOption, s.ContentType, s.Uri, s.Package.GetType().Name }) + .ToDictionary(s => s.Uri.ToString(), s => s); + + Assert.True(allParts["/xl/styles.xml"].ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"); + Assert.True(allParts["/xl/workbook.xml"].ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"); + Assert.True(allParts["/xl/worksheets/sheet1.xml"].ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"); + Assert.True(allParts["/xl/_rels/workbook.xml.rels"].ContentType == "application/vnd.openxmlformats-package.relationships+xml"); + Assert.True(allParts["/_rels/.rels"].ContentType == "application/vnd.openxmlformats-package.relationships+xml"); } File.Delete(path); } - [Fact()] + [Fact] public void TestStirctOpenXml() { - var path = @"../../../../../samples/xlsx/TestStrictOpenXml.xlsx"; + var path = "../../../../../samples/xlsx/TestStrictOpenXml.xlsx"; var columns = MiniExcel.GetColumns(path); Assert.Equal(new[] { "A", "B", "C" }, columns); @@ -1490,10 +1515,12 @@ public void InsertSheetTest() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, now); - table.Rows.Add(@"Hello World", -1234567890, false, now.Date); + table.Rows.Add("Hello World", -1234567890, false, now.Date); } - MiniExcel.Insert(path, table, sheetName: "Sheet1"); + var rowsWritten = MiniExcel.Insert(path, table, sheetName: "Sheet1"); + Assert.Equal(2, rowsWritten); + using (var p = new ExcelPackage(new FileInfo(path))) { var sheet1 = p.Workbook.Worksheets[0]; @@ -1520,7 +1547,9 @@ public void InsertSheetTest() table.Rows.Add("Github", 2); } - MiniExcel.Insert(path, table, sheetName: "Sheet2"); + var rowsWritten = MiniExcel.Insert(path, table, sheetName: "Sheet2"); + Assert.Equal(2, rowsWritten); + using (var p = new ExcelPackage(new FileInfo(path))) { var sheet2 = p.Workbook.Worksheets[1]; @@ -1545,7 +1574,7 @@ public void InsertSheetTest() table.Rows.Add("Test", now); } - MiniExcel.Insert(path, table, sheetName: "Sheet2", printHeader: false, configuration: new OpenXmlConfiguration + var rowsWritten = MiniExcel.Insert(path, table, sheetName: "Sheet2", printHeader: false, configuration: new OpenXmlConfiguration { FastMode = true, AutoFilter = false, @@ -1561,6 +1590,8 @@ public void InsertSheetTest() } } }, overwriteSheet: true); + Assert.Equal(1, rowsWritten); + using (var p = new ExcelPackage(new FileInfo(path))) { var sheet2 = p.Workbook.Worksheets[1]; @@ -1579,7 +1610,7 @@ public void InsertSheetTest() table.Rows.Add("Github", now); } - MiniExcel.Insert(path, table, sheetName: "Sheet3", configuration: new OpenXmlConfiguration + var rowsWritten = MiniExcel.Insert(path, table, sheetName: "Sheet3", configuration: new OpenXmlConfiguration { FastMode = true, AutoFilter = false, @@ -1595,6 +1626,8 @@ public void InsertSheetTest() } } }); + Assert.Equal(2, rowsWritten); + using (var p = new ExcelPackage(new FileInfo(path))) { var sheet3 = p.Workbook.Worksheets[2]; From b0d52385dd95401b0b7a60798e1e0aa88ca368d7 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Sat, 15 Mar 2025 01:56:41 +0100 Subject: [PATCH 2/4] Added cancellation token to async methods missing it - Fixed methods InsertAsync and SaveAsAsync not forwarding the cancellation token to the callees, preventing the actual cancellation of the processes from the outside - Added cancellation token support to a number of private and internal async methods, and increased the frequency of cancellation tokens checking whether they've been cancelled, in order to make the reach termination of the process as close as to when it's requested as possible --- src/MiniExcel/MiniExcel.Async.cs | 6 +- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 61 ++++- .../OpenXml/Styles/SheetStyleBuildContext.cs | 255 +++++++++--------- .../OpenXml/Styles/SheetStyleBuilderBase.cs | 65 +++-- 4 files changed, 208 insertions(+), 179 deletions(-) diff --git a/src/MiniExcel/MiniExcel.Async.cs b/src/MiniExcel/MiniExcel.Async.cs index 2ded0e30..6d026b66 100644 --- a/src/MiniExcel/MiniExcel.Async.cs +++ b/src/MiniExcel/MiniExcel.Async.cs @@ -43,12 +43,12 @@ public static async TaskInsertAsync(this Stream stream, object value, strin if (excelType == ExcelType.CSV) { var newValue = value is IEnumerable || value is IDataReader ? value : new[]{value}.AsEnumerable(); - return await ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false).InsertAsync(overwriteSheet); + return await ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false).InsertAsync(overwriteSheet, cancellationToken); } else { var configOrDefault = configuration ?? new OpenXmlConfiguration { FastMode = true }; - return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configOrDefault, printHeader).InsertAsync(overwriteSheet); + return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configOrDefault, printHeader).InsertAsync(overwriteSheet, cancellationToken); } } @@ -58,7 +58,7 @@ public static async TaskInsertAsync(this Stream stream, object value, strin throw new NotSupportedException("MiniExcel's SaveAs does not support the .xlsm format"); using (var stream = overwriteFile ? File.Create(path) : new FileStream(path, FileMode.CreateNew)) - return await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration); + return await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, cancellationToken); } public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index 76f2bed7..976153ed 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -28,6 +28,8 @@ public async Task SaveAsAsync(CancellationToken cancellationToken = defau foreach (var sheet in sheets) { + cancellationToken.ThrowIfCancellationRequested(); + _sheets.Add(sheet.Item1); //TODO:remove currentSheetIndex = sheet.Item1.SheetIdx; var rows = await CreateSheetXmlAsync(sheet.Item2, sheet.Item1.Path, cancellationToken); @@ -50,6 +52,10 @@ public async Task InsertAsync(bool overwriteSheet = false, CancellationToke if (!_configuration.FastMode) throw new InvalidOperationException("Insert requires fast mode to be enabled"); + cancellationToken.ThrowIfCancellationRequested(); + + var sheetRecords = new ExcelOpenXmlSheetReader(_stream, _configuration).GetWorkbookRels(_archive.Entries).ToArray(); + foreach (var sheetRecord in sheetRecords.OrderBy(o => o.Id)) { cancellationToken.ThrowIfCancellationRequested(); _sheets.Add(new SheetDto { Name = sheetRecord.Name, SheetIdx = (int)sheetRecord.Id, State = sheetRecord.State }); @@ -60,8 +66,6 @@ public async Task InsertAsync(bool overwriteSheet = false, CancellationToke await GenerateStylesXmlAsync(cancellationToken);//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 - if (existSheetDto == null) - var insertSheetInfo = GetSheetInfos(_defaultSheetName); int rowsWritten; if (existSheetDto == null) { @@ -166,6 +170,8 @@ private static async Task WriteDimensionAsync(MiniExcelAsyncStreamWriter writer, private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object values, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); + #if NETSTANDARD2_0_OR_GREATER || NET IMiniExcelWriteAdapter writeAdapter = null; if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) @@ -219,7 +225,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, obje } else { - await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props)); + await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken); } //header @@ -227,7 +233,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, obje var currentRowIndex = 0; if (_printHeader) { - await PrintHeaderAsync(writer, props); + await PrintHeaderAsync(writer, props, cancellationToken); currentRowIndex++; } @@ -235,9 +241,12 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, obje { foreach (var row in writeAdapter.GetRows(props, cancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)); foreach (var cellValue in row) { + cancellationToken.ThrowIfCancellationRequested(); await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths); } await writer.WriteAsync(WorksheetXml.EndRow); @@ -248,9 +257,12 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, obje { await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken)) { + cancellationToken.ThrowIfCancellationRequested(); await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)); + await foreach (var cellValue in row) { + cancellationToken.ThrowIfCancellationRequested(); await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths); } await writer.WriteAsync(WorksheetXml.EndRow); @@ -276,33 +288,40 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, obje } if (_configuration.EnableAutoWidth) { - await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns); + await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns, cancellationToken); } + + var toSubtract = _printHeader ? 1 : 0; + return maxRowIndex - toSubtract; } - private async Task WriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, ICollection props) + private static async Task WriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, ICollection props) { var placeholderPosition = await writer.FlushAsync(); await writer.WriteWhitespaceAsync(WorksheetXml.GetColumnPlaceholderLength(props.Count)); return placeholderPosition; } - private async Task OverWriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, long placeholderPosition, IEnumerable columnWidths) + private static async Task OverWriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, long placeholderPosition, IEnumerable columnWidths, CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); + var position = await writer.FlushAsync(); writer.SetPosition(placeholderPosition); - await WriteColumnsWidthsAsync(writer, columnWidths); + await WriteColumnsWidthsAsync(writer, columnWidths, cancellationToken); await writer.FlushAsync(); writer.SetPosition(position); } - private async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IEnumerable columnWidths) + private static async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IEnumerable columnWidths, CancellationToken cancellationToken = default) { var hasWrittenStart = false; foreach (var column in columnWidths) { + cancellationToken.ThrowIfCancellationRequested(); + if (!hasWrittenStart) { await writer.WriteAsync(WorksheetXml.StartCols); @@ -317,7 +336,7 @@ private async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IE await writer.WriteAsync(WorksheetXml.EndCols); } - private async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List props) + private async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List props, CancellationToken cancellationToken = default) { var xIndex = 1; var yIndex = 1; @@ -325,6 +344,8 @@ private async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List s.FullName == ExcelFileNames.ContentTypes); if (contentTypesZipEntry == null) { @@ -513,6 +542,8 @@ private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToke foreach (var p in _zipDictionary) { + cancellationToken.ThrowIfCancellationRequested(); + var partName = $"/{p.Key}"; if (!partNames.Contains(partName)) { @@ -528,7 +559,8 @@ private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToke private async Task CreateZipEntryAsync(string path, string contentType, string content, CancellationToken cancellationToken) { - ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); + cancellationToken.ThrowIfCancellationRequested(); + var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); using (var zipStream = entry.Open()) using (var writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken)) @@ -540,7 +572,8 @@ private async Task CreateZipEntryAsync(string path, string contentType, string c private async Task CreateZipEntryAsync(string path, byte[] content, CancellationToken cancellationToken) { - ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); + cancellationToken.ThrowIfCancellationRequested(); + var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); using (var zipStream = entry.Open()) await zipStream.WriteAsync(content, 0, content.Length, cancellationToken); diff --git a/src/MiniExcel/OpenXml/Styles/SheetStyleBuildContext.cs b/src/MiniExcel/OpenXml/Styles/SheetStyleBuildContext.cs index f8612142..32cf68b3 100644 --- a/src/MiniExcel/OpenXml/Styles/SheetStyleBuildContext.cs +++ b/src/MiniExcel/OpenXml/Styles/SheetStyleBuildContext.cs @@ -7,6 +7,7 @@ using System.IO.Compression; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Xml; @@ -15,7 +16,7 @@ namespace MiniExcelLibs.OpenXml.Styles internal class SheetStyleBuildContext : IDisposable { private static readonly string _emptyStylesXml = ExcelOpenXmlUtils.MinifyXml - ($@" + (@" " @@ -26,14 +27,14 @@ internal class SheetStyleBuildContext : IDisposable private readonly Encoding _encoding; private readonly ICollection _columns; - private StringReader emptyStylesXmlStringReader; - private ZipArchiveEntry oldStyleXmlZipEntry; - private ZipArchiveEntry newStyleXmlZipEntry; - private Stream oldXmlReaderStream; - private Stream newXmlWriterStream; - private bool initialized = false; - private bool finalized = false; - private bool disposed = false; + private StringReader _emptyStylesXmlStringReader; + private ZipArchiveEntry _oldStyleXmlZipEntry; + private ZipArchiveEntry _newStyleXmlZipEntry; + private Stream _oldXmlReaderStream; + private Stream _newXmlWriterStream; + private bool _initialized = false; + private bool _finalized = false; + private bool _disposed = false; public SheetStyleBuildContext(Dictionary zipDictionary, MiniExcelZipArchive archive, Encoding encoding, ICollection columns) { @@ -57,93 +58,94 @@ public SheetStyleBuildContext(Dictionary zipDictionary, public void Initialize(SheetStyleElementInfos generateElementInfos) { - if (initialized) + if (_initialized) { throw new InvalidOperationException("The context has been initialized."); } - oldStyleXmlZipEntry = _archive.Mode == ZipArchiveMode.Update ? _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Styles) : null; - if (oldStyleXmlZipEntry != null) + _oldStyleXmlZipEntry = _archive.Mode == ZipArchiveMode.Update ? _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Styles) : null; + if (_oldStyleXmlZipEntry != null) { - using (var oldStyleXmlStream = oldStyleXmlZipEntry.Open()) + using (var oldStyleXmlStream = _oldStyleXmlZipEntry.Open()) { OldElementInfos = ReadSheetStyleElementInfos(XmlReader.Create(oldStyleXmlStream, new XmlReaderSettings() { IgnoreWhitespace = true })); } - oldXmlReaderStream = oldStyleXmlZipEntry.Open(); - OldXmlReader = XmlReader.Create(oldXmlReaderStream, new XmlReaderSettings() { IgnoreWhitespace = true }); + _oldXmlReaderStream = _oldStyleXmlZipEntry.Open(); + OldXmlReader = XmlReader.Create(_oldXmlReaderStream, new XmlReaderSettings() { IgnoreWhitespace = true }); - newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles + ".temp", CompressionLevel.Fastest); + _newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles + ".temp", CompressionLevel.Fastest); } else { OldElementInfos = new SheetStyleElementInfos(); - emptyStylesXmlStringReader = new StringReader(_emptyStylesXml); - OldXmlReader = XmlReader.Create(emptyStylesXmlStringReader, new XmlReaderSettings() { IgnoreWhitespace = true }); + _emptyStylesXmlStringReader = new StringReader(_emptyStylesXml); + OldXmlReader = XmlReader.Create(_emptyStylesXmlStringReader, new XmlReaderSettings() { IgnoreWhitespace = true }); - newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles, CompressionLevel.Fastest); + _newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles, CompressionLevel.Fastest); } - newXmlWriterStream = newStyleXmlZipEntry.Open(); - NewXmlWriter = XmlWriter.Create(newXmlWriterStream, new XmlWriterSettings() { Indent = true, Encoding = _encoding }); + _newXmlWriterStream = _newStyleXmlZipEntry.Open(); + NewXmlWriter = XmlWriter.Create(_newXmlWriterStream, new XmlWriterSettings() { Indent = true, Encoding = _encoding }); GenerateElementInfos = generateElementInfos; ColumnsToApply = SheetStyleBuilderHelper.GenerateStyleIds(OldElementInfos.CellXfCount + generateElementInfos.CellXfCount, _columns).ToArray();//这里暂时加ToArray,避免多次计算,如果有性能问题再考虑优化 CustomFormatCount = ColumnsToApply.Count(); - initialized = true; + _initialized = true; } - public async Task InitializeAsync(SheetStyleElementInfos generateElementInfos) + public async Task InitializeAsync(SheetStyleElementInfos generateElementInfos, CancellationToken cancellationToken = default) { - if (initialized) + if (_initialized) { throw new InvalidOperationException("The context has been initialized."); } - - oldStyleXmlZipEntry = _archive.Mode == ZipArchiveMode.Update ? _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Styles) : null; - if (oldStyleXmlZipEntry != null) + cancellationToken.ThrowIfCancellationRequested(); + + _oldStyleXmlZipEntry = _archive.Mode == ZipArchiveMode.Update ? _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Styles) : null; + if (_oldStyleXmlZipEntry != null) { - using (var oldStyleXmlStream = oldStyleXmlZipEntry.Open()) + using (var oldStyleXmlStream = _oldStyleXmlZipEntry.Open()) { OldElementInfos = await ReadSheetStyleElementInfosAsync(XmlReader.Create(oldStyleXmlStream, new XmlReaderSettings() { IgnoreWhitespace = true, Async = true })); } - oldXmlReaderStream = oldStyleXmlZipEntry.Open(); - OldXmlReader = XmlReader.Create(oldXmlReaderStream, new XmlReaderSettings() { IgnoreWhitespace = true, Async = true }); + _oldXmlReaderStream = _oldStyleXmlZipEntry.Open(); + OldXmlReader = XmlReader.Create(_oldXmlReaderStream, new XmlReaderSettings() { IgnoreWhitespace = true, Async = true }); - newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles + ".temp", CompressionLevel.Fastest); + _newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles + ".temp", CompressionLevel.Fastest); } else { OldElementInfos = new SheetStyleElementInfos(); - emptyStylesXmlStringReader = new StringReader(_emptyStylesXml); - OldXmlReader = XmlReader.Create(emptyStylesXmlStringReader, new XmlReaderSettings() { IgnoreWhitespace = true, Async = true }); + _emptyStylesXmlStringReader = new StringReader(_emptyStylesXml); + OldXmlReader = XmlReader.Create(_emptyStylesXmlStringReader, new XmlReaderSettings() { IgnoreWhitespace = true, Async = true }); - newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles, CompressionLevel.Fastest); + _newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles, CompressionLevel.Fastest); } - newXmlWriterStream = newStyleXmlZipEntry.Open(); - NewXmlWriter = XmlWriter.Create(newXmlWriterStream, new XmlWriterSettings() { Indent = true, Encoding = _encoding, Async = true }); + _newXmlWriterStream = _newStyleXmlZipEntry.Open(); + NewXmlWriter = XmlWriter.Create(_newXmlWriterStream, new XmlWriterSettings() { Indent = true, Encoding = _encoding, Async = true }); GenerateElementInfos = generateElementInfos; ColumnsToApply = SheetStyleBuilderHelper.GenerateStyleIds(OldElementInfos.CellXfCount + generateElementInfos.CellXfCount, _columns).ToArray();//ToArray to avoid multiple calculations, if there is a performance problem then consider optimizing the CustomFormatCount = ColumnsToApply.Count(); - initialized = true; + _initialized = true; } public void FinalizeAndUpdateZipDictionary() { - if (!initialized) + if (!_initialized) { throw new InvalidOperationException("The context has not been initialized."); } - if (disposed) + if (_disposed) { throw new ObjectDisposedException(nameof(SheetStyleBuildContext)); } - if (finalized) + if (_finalized) { throw new InvalidOperationException("The context has been finalized."); } @@ -151,38 +153,38 @@ public void FinalizeAndUpdateZipDictionary() { OldXmlReader.Dispose(); OldXmlReader = null; - oldXmlReaderStream?.Dispose(); - oldXmlReaderStream = null; - emptyStylesXmlStringReader?.Dispose(); - emptyStylesXmlStringReader = null; + _oldXmlReaderStream?.Dispose(); + _oldXmlReaderStream = null; + _emptyStylesXmlStringReader?.Dispose(); + _emptyStylesXmlStringReader = null; NewXmlWriter.Flush(); NewXmlWriter.Close(); NewXmlWriter.Dispose(); NewXmlWriter = null; - newXmlWriterStream.Dispose(); - newXmlWriterStream = null; + _newXmlWriterStream.Dispose(); + _newXmlWriterStream = null; - if (oldStyleXmlZipEntry == null) + if (_oldStyleXmlZipEntry == null) { - _zipDictionary.Add(ExcelFileNames.Styles, new ZipPackageInfo(newStyleXmlZipEntry, ExcelContentTypes.Styles)); + _zipDictionary.Add(ExcelFileNames.Styles, new ZipPackageInfo(_newStyleXmlZipEntry, ExcelContentTypes.Styles)); } else { - oldStyleXmlZipEntry?.Delete(); - oldStyleXmlZipEntry = null; + _oldStyleXmlZipEntry?.Delete(); + _oldStyleXmlZipEntry = null; var finalStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles, CompressionLevel.Fastest); - using (var tempStream = newStyleXmlZipEntry.Open()) + using (var tempStream = _newStyleXmlZipEntry.Open()) using (var newStream = finalStyleXmlZipEntry.Open()) { tempStream.CopyTo(newStream); } _zipDictionary[ExcelFileNames.Styles] = new ZipPackageInfo(finalStyleXmlZipEntry, ExcelContentTypes.Styles); - newStyleXmlZipEntry.Delete(); - newStyleXmlZipEntry = null; + _newStyleXmlZipEntry.Delete(); + _newStyleXmlZipEntry = null; } - finalized = true; + _finalized = true; } catch (Exception ex) { @@ -190,56 +192,53 @@ public void FinalizeAndUpdateZipDictionary() } } - public async Task FinalizeAndUpdateZipDictionaryAsync() + public async Task FinalizeAndUpdateZipDictionaryAsync(CancellationToken cancellationToken = default) { - if (!initialized) - { + if (!_initialized) throw new InvalidOperationException("The context has not been initialized."); - } - if (disposed) - { + if (_disposed) throw new ObjectDisposedException(nameof(SheetStyleBuildContext)); - } - if (finalized) - { + if (_finalized) throw new InvalidOperationException("The context has been finalized."); - } + try { + cancellationToken.ThrowIfCancellationRequested(); + OldXmlReader.Dispose(); OldXmlReader = null; - oldXmlReaderStream?.Dispose(); - oldXmlReaderStream = null; - emptyStylesXmlStringReader?.Dispose(); - emptyStylesXmlStringReader = null; + _oldXmlReaderStream?.Dispose(); + _oldXmlReaderStream = null; + _emptyStylesXmlStringReader?.Dispose(); + _emptyStylesXmlStringReader = null; await NewXmlWriter.FlushAsync(); NewXmlWriter.Close(); NewXmlWriter.Dispose(); NewXmlWriter = null; - newXmlWriterStream.Dispose(); - newXmlWriterStream = null; + _newXmlWriterStream.Dispose(); + _newXmlWriterStream = null; - if (oldStyleXmlZipEntry == null) + if (_oldStyleXmlZipEntry == null) { - _zipDictionary.Add(ExcelFileNames.Styles, new ZipPackageInfo(newStyleXmlZipEntry, ExcelContentTypes.Styles)); + _zipDictionary.Add(ExcelFileNames.Styles, new ZipPackageInfo(_newStyleXmlZipEntry, ExcelContentTypes.Styles)); } else { - oldStyleXmlZipEntry?.Delete(); - oldStyleXmlZipEntry = null; + _oldStyleXmlZipEntry?.Delete(); + _oldStyleXmlZipEntry = null; var finalStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles, CompressionLevel.Fastest); - using (var tempStream = newStyleXmlZipEntry.Open()) + using (var tempStream = _newStyleXmlZipEntry.Open()) using (var newStream = finalStyleXmlZipEntry.Open()) { - await tempStream.CopyToAsync(newStream); + await tempStream.CopyToAsync(newStream, 4096, cancellationToken); } _zipDictionary[ExcelFileNames.Styles] = new ZipPackageInfo(finalStyleXmlZipEntry, ExcelContentTypes.Styles); - newStyleXmlZipEntry.Delete(); - newStyleXmlZipEntry = null; + _newStyleXmlZipEntry.Delete(); + _newStyleXmlZipEntry = null; } - finalized = true; + _finalized = true; } catch (Exception ex) { @@ -257,11 +256,12 @@ private static SheetStyleElementInfos ReadSheetStyleElementInfos(XmlReader reade return elementInfos; } - private static async Task ReadSheetStyleElementInfosAsync(XmlReader reader) + private static async Task ReadSheetStyleElementInfosAsync(XmlReader reader, CancellationToken cancellationToken = default) { var elementInfos = new SheetStyleElementInfos(); while (await reader.ReadAsync()) { + cancellationToken.ThrowIfCancellationRequested(); SetElementInfos(reader, elementInfos); } return elementInfos; @@ -269,71 +269,68 @@ private static async Task ReadSheetStyleElementInfosAsyn private static void SetElementInfos(XmlReader reader, SheetStyleElementInfos elementInfos) { - if (reader.NodeType == XmlNodeType.Element) + if (reader.NodeType != XmlNodeType.Element) + return; + + switch (reader.LocalName) { - switch (reader.LocalName) - { - case "numFmts": - elementInfos.ExistsNumFmts = true; - elementInfos.NumFmtCount = GetCount(); - break; - case "fonts": - elementInfos.ExistsFonts = true; - elementInfos.FontCount = GetCount(); - break; - case "fills": - elementInfos.ExistsFills = true; - elementInfos.FillCount = GetCount(); - break; - case "borders": - elementInfos.ExistsBorders = true; - elementInfos.BorderCount = GetCount(); - break; - case "cellStyleXfs": - elementInfos.ExistsCellStyleXfs = true; - elementInfos.CellStyleXfCount = GetCount(); - break; - case "cellXfs": - elementInfos.ExistsCellXfs = true; - elementInfos.CellXfCount = GetCount(); - break; - } + case "numFmts": + elementInfos.ExistsNumFmts = true; + elementInfos.NumFmtCount = GetCount(); + break; + case "fonts": + elementInfos.ExistsFonts = true; + elementInfos.FontCount = GetCount(); + break; + case "fills": + elementInfos.ExistsFills = true; + elementInfos.FillCount = GetCount(); + break; + case "borders": + elementInfos.ExistsBorders = true; + elementInfos.BorderCount = GetCount(); + break; + case "cellStyleXfs": + elementInfos.ExistsCellStyleXfs = true; + elementInfos.CellStyleXfCount = GetCount(); + break; + case "cellXfs": + elementInfos.ExistsCellXfs = true; + elementInfos.CellXfCount = GetCount(); + break; + } - int GetCount() - { - string count = reader.GetAttribute("count"); - if (!string.IsNullOrEmpty(count) && int.TryParse(count, out int countValue)) - { - return countValue; - } - return 0; - } + int GetCount() + { + string count = reader.GetAttribute("count"); + if (!string.IsNullOrEmpty(count) && int.TryParse(count, out int countValue)) + return countValue; + return 0; } } public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { - if (!disposed) + if (_disposed) + return; + + if (disposing) { - if (disposing) - { - OldXmlReader?.Dispose(); - oldXmlReaderStream?.Dispose(); - emptyStylesXmlStringReader?.Dispose(); + OldXmlReader?.Dispose(); + _oldXmlReaderStream?.Dispose(); + _emptyStylesXmlStringReader?.Dispose(); - NewXmlWriter?.Dispose(); - newXmlWriterStream?.Dispose(); - } - - disposed = true; + NewXmlWriter?.Dispose(); + _newXmlWriterStream?.Dispose(); } + + _disposed = true; } } } diff --git a/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderBase.cs b/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderBase.cs index f056cec3..4f2cdb87 100644 --- a/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderBase.cs +++ b/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderBase.cs @@ -86,10 +86,11 @@ public virtual SheetStyleBuildResult Build() return new SheetStyleBuildResult(GetCellXfIdMap()); } - + + // Todo: add CancellationToken to all methods called inside of BuildAsync public virtual async Task BuildAsync(CancellationToken cancellationToken = default) { - await _context.InitializeAsync(GetGenerateElementInfos()); + await _context.InitializeAsync(GetGenerateElementInfos(), cancellationToken); while (await _context.OldXmlReader.ReadAsync()) { @@ -100,7 +101,7 @@ public virtual async Task BuildAsync(CancellationToken ca case XmlNodeType.Element: await GenerateElementBeforStartElementAsync(); await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, _context.OldXmlReader.LocalName, _context.OldXmlReader.NamespaceURI); - await WriteAttributesAsync(_context.OldXmlReader.LocalName); + await WriteAttributesAsync(_context.OldXmlReader.LocalName, cancellationToken); if (_context.OldXmlReader.IsEmptyElement) { await GenerateElementBeforEndElementAsync(); @@ -137,7 +138,7 @@ public virtual async Task BuildAsync(CancellationToken ca } } - await _context.FinalizeAndUpdateZipDictionaryAsync(); + await _context.FinalizeAndUpdateZipDictionaryAsync(cancellationToken); return new SheetStyleBuildResult(GetCellXfIdMap()); } @@ -204,13 +205,13 @@ protected virtual void WriteAttributes(string element) } } - protected virtual async Task WriteAttributesAsync(string element) + protected virtual async Task WriteAttributesAsync(string element, CancellationToken cancellationToken = default) { if (_context.OldXmlReader.NodeType is XmlNodeType.Element || _context.OldXmlReader.NodeType is XmlNodeType.XmlDeclaration) { if (_context.OldXmlReader.MoveToFirstAttribute()) { - await WriteAttributesAsync(element); + await WriteAttributesAsync(element, cancellationToken); _context.OldXmlReader.MoveToElement(); } } @@ -222,6 +223,8 @@ protected virtual async Task WriteAttributesAsync(string element) var currentAttribute = _context.OldXmlReader.LocalName; while (_context.OldXmlReader.ReadAttributeValue()) { + cancellationToken.ThrowIfCancellationRequested(); + if (_context.OldXmlReader.NodeType == XmlNodeType.EntityReference) { await _context.NewXmlWriter.WriteEntityRefAsync(_context.OldXmlReader.Name); @@ -374,33 +377,29 @@ protected virtual void GenerateElementBeforEndElement() protected virtual async Task GenerateElementBeforEndElementAsync() { - if (_context.OldXmlReader.LocalName == "styleSheet" && !_context.OldElementInfos.ExistsNumFmts && !_context.GenerateElementInfos.ExistsNumFmts) - { - await GenerateNumFmtsAsync(); - } - else if (_context.OldXmlReader.LocalName == "numFmts") - { - await GenerateNumFmtAsync(); - } - else if (_context.OldXmlReader.LocalName == "fonts") - { - await GenerateFontAsync(); - } - else if (_context.OldXmlReader.LocalName == "fills") - { - await GenerateFillAsync(); - } - else if (_context.OldXmlReader.LocalName == "borders") - { - await GenerateBorderAsync(); - } - else if (_context.OldXmlReader.LocalName == "cellStyleXfs") - { - await GenerateCellStyleXfAsync(); - } - else if (_context.OldXmlReader.LocalName == "cellXfs") - { - await GenerateCellXfAsync(); + switch (_context.OldXmlReader.LocalName) + { + case "styleSheet" when !_context.OldElementInfos.ExistsNumFmts && !_context.GenerateElementInfos.ExistsNumFmts: + await GenerateNumFmtsAsync(); + break; + case "numFmts": + await GenerateNumFmtAsync(); + break; + case "fonts": + await GenerateFontAsync(); + break; + case "fills": + await GenerateFillAsync(); + break; + case "borders": + await GenerateBorderAsync(); + break; + case "cellStyleXfs": + await GenerateCellStyleXfAsync(); + break; + case "cellXfs": + await GenerateCellXfAsync(); + break; } } From eb56bb072cacb498d955e4e53e186565f7e3f761 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Sat, 15 Mar 2025 15:35:31 +0100 Subject: [PATCH 3/4] Addendum Some more renaming / cleaning up, especially in the tests --- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 18 +- .../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 2 +- .../OpenXml/ExcelOpenXmlSheetWriter.cs | 38 +-- .../MiniExcelIssueAsyncTests.cs | 188 +++++++------ tests/MiniExcelTests/MiniExcelIssueTests.cs | 260 +++++++++++------- 5 files changed, 292 insertions(+), 214 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index 976153ed..eda438a1 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -31,7 +31,7 @@ public async Task SaveAsAsync(CancellationToken cancellationToken = defau cancellationToken.ThrowIfCancellationRequested(); _sheets.Add(sheet.Item1); //TODO:remove - currentSheetIndex = sheet.Item1.SheetIdx; + _currentSheetIndex = sheet.Item1.SheetIdx; var rows = await CreateSheetXmlAsync(sheet.Item2, sheet.Item1.Path, cancellationToken); rowsWritten.Add(rows); } @@ -69,26 +69,26 @@ public async Task InsertAsync(bool overwriteSheet = false, CancellationToke int rowsWritten; if (existSheetDto == null) { - currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; + _currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; var insertSheetInfo = GetSheetInfos(_defaultSheetName); - var insertSheetDto = insertSheetInfo.ToDto(currentSheetIndex); + var insertSheetDto = insertSheetInfo.ToDto(_currentSheetIndex); _sheets.Add(insertSheetDto); rowsWritten = await CreateSheetXmlAsync(_value, insertSheetDto.Path, cancellationToken); } else { - currentSheetIndex = existSheetDto.SheetIdx; + _currentSheetIndex = existSheetDto.SheetIdx; _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); rowsWritten = await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken); } await AddFilesToZipAsync(cancellationToken); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex - 1))?.Delete(); - await GenerateDrawinRelXmlAsync(currentSheetIndex - 1, cancellationToken); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(_currentSheetIndex - 1))?.Delete(); + await GenerateDrawinRelXmlAsync(_currentSheetIndex - 1, cancellationToken); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex - 1))?.Delete(); - await GenerateDrawingXmlAsync(currentSheetIndex - 1, cancellationToken); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(_currentSheetIndex - 1))?.Delete(); + await GenerateDrawingXmlAsync(_currentSheetIndex - 1, cancellationToken); GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary sheetsRelsXml); foreach (var sheetRelsXml in sheetsRelsXml) @@ -272,7 +272,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, obje maxRowIndex = currentRowIndex; - await writer.WriteAsync(WorksheetXml.Drawing(currentSheetIndex)); + await writer.WriteAsync(WorksheetXml.Drawing(_currentSheetIndex)); await writer.WriteAsync(WorksheetXml.EndSheetData); if (_configuration.AutoFilter) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index db031596..d90d3af3 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -285,7 +285,7 @@ private string GetFileValue(int rowIndex, int cellIndex, object value) Byte = bytes, RowIndex = rowIndex, CellIndex = cellIndex, - SheetId = currentSheetIndex + SheetId = _currentSheetIndex }; if (format != ImageFormat.unknown) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 66e864d8..6bbc4f71 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -26,7 +26,7 @@ internal partial class ExcelOpenXmlSheetWriter : IExcelWriter private readonly string _defaultSheetName; private readonly List _sheets = new List(); private readonly List _files = new List(); - private int currentSheetIndex = 0; + private int _currentSheetIndex = 0; public ExcelOpenXmlSheetWriter(Stream stream, object value, string sheetName, IConfiguration configuration, bool printHeader) { @@ -59,7 +59,7 @@ public int[] SaveAs() foreach (var sheet in sheets) { _sheets.Add(sheet.Item1); //TODO:remove - currentSheetIndex = sheet.Item1.SheetIdx; + _currentSheetIndex = sheet.Item1.SheetIdx; var rows = CreateSheetXml(sheet.Item2, sheet.Item1.Path); rowsWritten.Add(rows); } @@ -92,26 +92,26 @@ public int Insert(bool overwriteSheet = false) int rowsWritten; if (existSheetDto == null) { - currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; + _currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; var insertSheetInfo = GetSheetInfos(_defaultSheetName); - var insertSheetDto = insertSheetInfo.ToDto(currentSheetIndex); + var insertSheetDto = insertSheetInfo.ToDto(_currentSheetIndex); _sheets.Add(insertSheetDto); rowsWritten = CreateSheetXml(_value, insertSheetDto.Path); } else { - currentSheetIndex = existSheetDto.SheetIdx; + _currentSheetIndex = existSheetDto.SheetIdx; _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); rowsWritten = CreateSheetXml(_value, existSheetDto.Path); } AddFilesToZip(); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex - 1))?.Delete(); - GenerateDrawinRelXml(currentSheetIndex - 1); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(_currentSheetIndex - 1))?.Delete(); + GenerateDrawinRelXml(_currentSheetIndex - 1); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex - 1))?.Delete(); - GenerateDrawingXml(currentSheetIndex - 1); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(_currentSheetIndex - 1))?.Delete(); + GenerateDrawingXml(_currentSheetIndex - 1); GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary sheetsRelsXml); foreach (var sheetRelsXml in sheetsRelsXml) @@ -121,10 +121,10 @@ public int Insert(bool overwriteSheet = false) CreateZipEntry(sheetRelsXmlPath, null, ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value)); } - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook).Delete(); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete(); CreateZipEntry(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString())); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels).Delete(); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); CreateZipEntry(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString())); InsertContentTypesXml(); @@ -258,7 +258,7 @@ private int WriteValues(MiniExcelStreamWriter writer, object values) writer.Write(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); } - writer.Write(WorksheetXml.Drawing(currentSheetIndex)); + writer.Write(WorksheetXml.Drawing(_currentSheetIndex)); writer.Write(WorksheetXml.EndWorksheet); if (_configuration.FastMode && dimensionPlaceholderPostition != 0) @@ -267,7 +267,7 @@ private int WriteValues(MiniExcelStreamWriter writer, object values) } if (_configuration.EnableAutoWidth) { - OverWriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths.Columns); + OverWriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths?.Columns); } var toSubtract = _printHeader ? 1 : 0; @@ -348,7 +348,9 @@ private void WriteCell(MiniExcelStreamWriter writer, int rowIndex, int cellIndex } var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); - var valueIsNull = value is null || value is DBNull || (_configuration.WriteEmptyStringAsNull && value is String && value == string.Empty); + var valueIsNull = value is null || + value is DBNull || + (_configuration.WriteEmptyStringAsNull && value is string vs && vs == string.Empty); if (_configuration.EnableWriteNullValueCell && valueIsNull) { @@ -408,8 +410,8 @@ private void GenerateStylesXml() builder = new DefaultSheetStyleBuilder(context); break; } - var result = builder.Build(); - _cellXfIdMap = result.CellXfIdMap; + var result = builder?.Build(); + _cellXfIdMap = result?.CellXfIdMap; } } @@ -491,10 +493,10 @@ private void InsertContentTypesXml() using (var stream = contentTypesZipEntry.Open()) { var doc = XDocument.Load(stream); - var ns = doc.Root.GetDefaultNamespace(); + var ns = doc.Root?.GetDefaultNamespace(); var typesElement = doc.Descendants(ns + "Types").Single(); var partNames = new HashSet(StringComparer.InvariantCultureIgnoreCase); - foreach (var partName in typesElement.Elements(ns + "Override").Select(s => s.Attribute("PartName").Value)) + foreach (var partName in typesElement.Elements(ns + "Override").Select(s => s.Attribute("PartName")?.Value)) { partNames.Add(partName); } diff --git a/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs b/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs index cbc7f318..89bfd032 100644 --- a/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs @@ -22,10 +22,10 @@ namespace MiniExcelLibs.Tests { public partial class MiniExcelIssueAsyncTests { - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper _output; public MiniExcelIssueAsyncTests(ITestOutputHelper output) { - this.output = output; + _output = output; } /// @@ -41,7 +41,8 @@ public async Task Issue255() var path = PathHelper.GetTempPath(); var value = new { - Issue255DTO = new Issue255DTO[] { + Issue255DTO = new [] + { new Issue255DTO { Time = new DateTime(2021, 01, 01), Time2 = new DateTime(2021, 01, 01) } } }; @@ -134,9 +135,9 @@ public async Task Issue253() Assert.Equal(expected, File.ReadAllText(path)); } - using (var cn = Db.GetConnection()) + await using (var cn = Db.GetConnection()) { - var value = cn.ExecuteReader(@"select '世界你好' col1"); + var value = await cn.ExecuteReaderAsync("select '世界你好' col1"); var path = PathHelper.GetTempPath(extension: "csv"); await MiniExcel.SaveAsAsync(path, value); var expected = @@ -155,7 +156,7 @@ public async Task Issue253() [Fact] public async Task Issue251() { - using (var cn = Db.GetConnection()) + await using (var cn = Db.GetConnection()) { var reader = await cn.ExecuteReaderAsync(@"select '""<>+-*//}{\\n' a,1234567890 b union all select 'Hello World',-1234567890"); var path = PathHelper.GetTempPath(extension: "csv"); @@ -182,19 +183,11 @@ public async Task Issue242() { var path = PathHelper.GetFile("xls/TestIssue242.xls"); - await Assert.ThrowsAsync(async () => - { - var q = await MiniExcel.QueryAsync(path); - q.ToList(); - }); + await Assert.ThrowsAsync(async () => (await MiniExcel.QueryAsync(path)).ToList()); - using (var stream = File.OpenRead(path)) + await using (var stream = File.OpenRead(path)) { - await Assert.ThrowsAsync(async () => - { - var q = await stream.QueryAsync(); - q.ToList(); - }); + await Assert.ThrowsAsync(async () => (await stream.QueryAsync()).ToList()); } } @@ -356,7 +349,7 @@ public async Task Issue235() var sheets = new DataSet(); var users = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { Name = "Jack", Age = 25 }, new { Name = "Mike", Age = 44 } })); users.TableName = "users"; - var department = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } })); ; + var department = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } })); department.TableName = "department"; sheets.Tables.Add(users); sheets.Tables.Add(department); @@ -394,9 +387,13 @@ public async Task Issue235() public async Task Issue233() { var path = PathHelper.GetFile("xlsx/TestIssue233.xlsx"); + +#pragma warning disable CS0618 // Type or member is obsolete var dt = await MiniExcel.QueryAsDataTableAsync(path); +#pragma warning restore CS0618 // Type or member is obsolete + var rows = dt.Rows; - + Assert.Equal(0.55, rows[0]["Size"]); Assert.Equal("0.55/1.1", rows[1]["Size"]); } @@ -475,14 +472,14 @@ public async Task Issue230() conn.Open(); var cmd = conn.CreateCommand(); cmd.CommandText = "select 1 id union all select 2"; - using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) + await using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { - while (reader.Read()) + while (await reader.ReadAsync()) { for (int i = 0; i < reader.FieldCount; i++) { var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; - output.WriteLine(result); + _output.WriteLine(result); } } } @@ -491,14 +488,14 @@ public async Task Issue230() conn.Open(); cmd = conn.CreateCommand(); cmd.CommandText = "select 1 id union all select 2"; - using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) + await using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { - while (reader.Read()) + while (await reader.ReadAsync()) { for (int i = 0; i < reader.FieldCount; i++) { var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; - output.WriteLine(result); + _output.WriteLine(result); } } } @@ -507,7 +504,7 @@ public async Task Issue230() conn.Open(); cmd = conn.CreateCommand(); cmd.CommandText = "select 1 id union all select 2"; - using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) + await using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { var path = PathHelper.GetTempPath(); var rowsWritten = await MiniExcel.SaveAsAsync(path, reader, printHeader: true); @@ -529,7 +526,11 @@ public async Task Issue230() public async Task Issue229() { var path = PathHelper.GetFile("xlsx/TestIssue229.xlsx"); + +#pragma warning disable CS0618 // Type or member is obsolete var dt = await MiniExcel.QueryAsDataTableAsync(path); +#pragma warning restore CS0618 // Type or member is obsolete + foreach (DataColumn column in dt.Columns) { var v = dt.Rows[3][column]; @@ -644,18 +645,20 @@ public async Task Issue226() [Fact] public async Task Issue223() { - var value = new List>() - { - new Dictionary(){{"A",null},{"B",null}}, - new Dictionary(){{"A",123},{"B",new DateTime(2021,1,1)}}, - new Dictionary(){{"A",Guid.NewGuid()},{"B","HelloWorld"}}, - }; + List> value = + [ + new() { { "A", null }, { "B", null } }, + new() { { "A", 123 }, { "B", new DateTime(2021, 1, 1) } }, + new() { { "A", Guid.NewGuid() }, { "B", "HelloWorld" } } + ]; var path = PathHelper.GetTempPath(); - var rowsWritten = MiniExcel.SaveAs(path, value); + var rowsWritten = await MiniExcel.SaveAsAsync(path, value); Assert.Single(rowsWritten); Assert.Equal(3, rowsWritten[0]); +#pragma warning disable CS0618 // Type or member is obsolete var dt = await MiniExcel.QueryAsDataTableAsync(path); +#pragma warning restore CS0618 // Type or member is obsolete var columns = dt.Columns; Assert.Equal(typeof(object), columns[0].DataType); Assert.Equal(typeof(object), columns[1].DataType); @@ -689,39 +692,39 @@ public async Task Issue147() var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); var q = await MiniExcel.QueryAsync(path, useHeaderRow: false, startCell: "C3", sheetName: "Sheet1"); var rows = q.ToList(); - Assert.Equal(new[] { "C", "D", "E" }, (rows[0] as IDictionary).Keys); - Assert.Equal(new[] { "Column1", "Column2", "Column3" }, new[] { rows[0].C as string, rows[0].D as string, rows[0].E as string }); - Assert.Equal(new[] { "C4", "D4", "E4" }, new[] { rows[1].C as string, rows[1].D as string, rows[1].E as string }); - Assert.Equal(new[] { "C9", "D9", "E9" }, new[] { rows[6].C as string, rows[6].D as string, rows[6].E as string }); - Assert.Equal(new[] { "C12", "D12", "E12" }, new[] { rows[9].C as string, rows[9].D as string, rows[9].E as string }); - Assert.Equal(new[] { "C13", "D13", "E13" }, new[] { rows[10].C as string, rows[10].D as string, rows[10].E as string }); + Assert.Equal(["C", "D", "E"], (rows[0] as IDictionary)?.Keys); + Assert.Equal(["Column1", "Column2", "Column3"], new[] { rows[0].C as string, rows[0].D as string, rows[0].E as string }); + Assert.Equal(["C4", "D4", "E4"], new[] { rows[1].C as string, rows[1].D as string, rows[1].E as string }); + Assert.Equal(["C9", "D9", "E9"], new[] { rows[6].C as string, rows[6].D as string, rows[6].E as string }); + Assert.Equal(["C12", "D12", "E12"], new[] { rows[9].C as string, rows[9].D as string, rows[9].E as string }); + Assert.Equal(["C13", "D13", "E13"], new[] { rows[10].C as string, rows[10].D as string, rows[10].E as string }); foreach (var i in new[] { 4, 5, 7, 8 }) - Assert.Equal(new[] { default(string), default(string), default(string) }, new[] { rows[i].C as string, rows[i].D as string, rows[i].E as string }); - + { + Assert.Equal([null, null, null], new[] { rows[i].C as string, rows[i].D as string, rows[i].E as string }); + } Assert.Equal(11, rows.Count); - var columns = MiniExcel.GetColumns(path, startCell: "C3"); - Assert.Equal(new[] { "C", "D", "E" }, columns); + Assert.Equal(["C", "D", "E"], columns); } { var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); var q = await MiniExcel.QueryAsync(path, useHeaderRow: true, startCell: "C3", sheetName: "Sheet1"); var rows = q.ToList(); - Assert.Equal(new[] { "Column1", "Column2", "Column3" }, (rows[0] as IDictionary).Keys); - Assert.Equal(new[] { "C4", "D4", "E4" }, new[] { rows[0].Column1 as string, rows[0].Column2 as string, rows[0].Column3 as string }); - Assert.Equal(new[] { "C9", "D9", "E9" }, new[] { rows[5].Column1 as string, rows[5].Column2 as string, rows[5].Column3 as string }); - Assert.Equal(new[] { "C12", "D12", "E12" }, new[] { rows[8].Column1 as string, rows[8].Column2 as string, rows[8].Column3 as string }); - Assert.Equal(new[] { "C13", "D13", "E13" }, new[] { rows[9].Column1 as string, rows[9].Column2 as string, rows[9].Column3 as string }); + Assert.Equal(["Column1", "Column2", "Column3"], (rows[0] as IDictionary)?.Keys); + Assert.Equal(["C4", "D4", "E4"], new[] { rows[0].Column1 as string, rows[0].Column2 as string, rows[0].Column3 as string }); + Assert.Equal(["C9", "D9", "E9"], new[] { rows[5].Column1 as string, rows[5].Column2 as string, rows[5].Column3 as string }); + Assert.Equal(["C12", "D12", "E12"], new[] { rows[8].Column1 as string, rows[8].Column2 as string, rows[8].Column3 as string }); + Assert.Equal(["C13", "D13", "E13"], new[] { rows[9].Column1 as string, rows[9].Column2 as string, rows[9].Column3 as string }); foreach (var i in new[] { 3, 4, 6, 7 }) - Assert.Equal(new[] { default(string), default(string), default(string) }, new[] { rows[i].Column1 as string, rows[i].Column2 as string, rows[i].Column3 as string }); - + { + Assert.Equal([null, null, null], new[] { rows[i].Column1 as string, rows[i].Column2 as string, rows[i].Column3 as string }); + } Assert.Equal(10, rows.Count); - - + var columns = MiniExcel.GetColumns(path, useHeaderRow: true, startCell: "C3"); - Assert.Equal(new[] { "Column1", "Column2", "Column3" }, columns); + Assert.Equal(["Column1", "Column2", "Column3"], columns); } } @@ -737,11 +740,11 @@ public async Task Issue211() var tempSqlitePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); var connectionString = $"Data Source={tempSqlitePath};Version=3;"; - using (var connection = new SQLiteConnection(connectionString)) + await using (var connection = new SQLiteConnection(connectionString)) { - var reader = connection.ExecuteReader("select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); + var reader = await connection.ExecuteReaderAsync("select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); - var rowsWritten = MiniExcel.SaveAs(path, reader); + var rowsWritten = await MiniExcel.SaveAsAsync(path, reader); Assert.Single(rowsWritten); Assert.Equal(3, rowsWritten[0]); @@ -760,7 +763,7 @@ public async Task EmptyDataReaderIssue() var path = PathHelper.GetTempPath(); var reader = Substitute.For(); - var rowsWritten = MiniExcel.SaveAs(path, reader, overwriteFile: true); + var rowsWritten = await MiniExcel.SaveAsAsync(path, reader, overwriteFile: true); Assert.Single(rowsWritten); Assert.Equal(0, rowsWritten[0]); @@ -777,10 +780,15 @@ public async Task Issue216() { var path = PathHelper.GetTempPath(); var value = new[] { new { Test1 = "1", Test2 = 2 }, new { Test1 = "3", Test2 = 4 } }; - MiniExcel.SaveAs(path, value); + var rowsWritten = await MiniExcel.SaveAsAsync(path, value); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); { +#pragma warning disable CS0618 // Type or member is obsolete var table = await MiniExcel.QueryAsDataTableAsync(path); +#pragma warning restore CS0618 // Type or member is obsolete + var columns = table.Columns; Assert.Equal("Test1", table.Columns[0].ColumnName); Assert.Equal("Test2", table.Columns[1].ColumnName); @@ -791,7 +799,10 @@ public async Task Issue216() } { +#pragma warning disable CS0618 // Type or member is obsolete var dt = await MiniExcel.QueryAsDataTableAsync(path, false); +#pragma warning restore CS0618 // Type or member is obsolete + Assert.Equal("Test1", dt.Rows[0]["A"]); Assert.Equal("Test2", dt.Rows[0]["B"]); Assert.Equal("1", dt.Rows[1]["A"]); @@ -864,9 +875,9 @@ group s by s.PRT_ID into g [Fact] public async Task Issue215() { - using (var stream = new MemoryStream()) + await using (var stream = new MemoryStream()) { - stream.SaveAs(new[] { new { V = "test1" }, new { V = "test2" } }); + await stream.SaveAsAsync(new[] { new { V = "test1" }, new { V = "test2" } }); var q = (await stream.QueryAsync(true)).Cast>(); var rows = q.ToList(); Assert.Equal("test1", rows[0]["V"]); @@ -892,17 +903,17 @@ public async Task Issue89() """; var stream = new MemoryStream(); var writer = new StreamWriter(stream); - writer.Write(text); - writer.Flush(); + await writer.WriteAsync(text); + await writer.FlushAsync(); stream.Position = 0; - var q = await MiniExcel.QueryAsync(stream, excelType: ExcelType.CSV); + var q = await stream.QueryAsync(excelType: ExcelType.CSV); var rows = q.ToList(); Assert.Equal(Issue89VO.WorkState.OnDuty, rows[0].State); Assert.Equal(Issue89VO.WorkState.Fired, rows[1].State); Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); var outputPath = PathHelper.GetTempPath("xlsx"); - var rowsWritten = MiniExcel.SaveAs(outputPath, rows); + var rowsWritten = await MiniExcel.SaveAsAsync(outputPath, rows); Assert.Single(rowsWritten); Assert.Equal(3, rowsWritten[0]); @@ -923,7 +934,7 @@ public async Task Issue89() Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); var outputPath = PathHelper.GetTempPath(); - var rowsWritten = MiniExcel.SaveAs(outputPath, rows); + var rowsWritten = await MiniExcel.SaveAsAsync(outputPath, rows); Assert.Single(rowsWritten); Assert.Equal(3, rowsWritten[0]); @@ -955,17 +966,19 @@ public enum WorkState [Fact] public async Task Issue217() { - DataTable table = new DataTable(); + using var table = new DataTable(); table.Columns.Add("CustomerID"); table.Columns.Add("CustomerName").Caption = "Name"; table.Columns.Add("CreditLimit").Caption = "Limit"; - table.Rows.Add(new object[] { 1, "Jonathan", 23.44 }); - table.Rows.Add(new object[] { 2, "Bill", 56.87 }); + table.Rows.Add(1, "Jonathan", 23.44); + table.Rows.Add(2, "Bill", 56.87); // openxml { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - MiniExcel.SaveAs(path, table); + var rowsWritten = await MiniExcel.SaveAsAsync(path, table); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); var q = await MiniExcel.QueryAsync(path); var rows = q.ToList(); @@ -985,7 +998,6 @@ public async Task Issue217() Assert.Equal("Name", rows[0].B); Assert.Equal("Limit", rows[0].C); - File.Delete(path); } } @@ -1009,7 +1021,7 @@ public async Task Issue212() } /// - /// Version <= v0.13.1 Template merge row list rendering has no merge + /// Version <= v0.13.1 Template merge row list rendering has no merge /// https://github.com/shps951023/MiniExcel/issues/207 /// [Fact] @@ -1017,7 +1029,7 @@ public async Task Issue207() { { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); - var tempaltePath = @"../../../../../samples/xlsx/TestIssue207_2.xlsx"; + var tempaltePath = "../../../../../samples/xlsx/TestIssue207_2.xlsx"; var value = new { @@ -1106,7 +1118,7 @@ public async Task Issue87() { Tests = Enumerable.Range(1, 5).Select((s, i) => new { test1 = i, test2 = i }) }; - using (var stream = File.OpenRead(templatePath)) + await using (var stream = File.OpenRead(templatePath)) { var q = await MiniExcel.QueryAsync(templatePath); var rows = q.ToList(); @@ -1350,7 +1362,10 @@ public async Task Issue142() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.csv"); - var input = new Issue142VoDuplicateColumnName[] { new Issue142VoDuplicateColumnName { } }; + Issue142VoDuplicateColumnName[] input = + [ + new() { MyProperty1 = 0, MyProperty2 = 0, MyProperty3 = 0, MyProperty4 = 0 } + ]; Assert.Throws(() => MiniExcel.SaveAs(path, input)); } } @@ -1555,7 +1570,7 @@ public async Task Issue149() var rows = q.Select(s => (string)s.Test).ToList(); for (int i = 0; i < chars.Length; i++) { - output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); + _output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); if (i == 13 || i == 9 || i == 10) continue; Assert.Equal(chars[i], rows[i]); @@ -1571,7 +1586,7 @@ public async Task Issue149() var rows = q.Select(s => s.Test).ToList(); for (int i = 0; i < chars.Length; i++) { - output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); + _output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); if (i == 13 || i == 9 || i == 10) continue; Assert.Equal(chars[i], rows[i]); @@ -1594,8 +1609,11 @@ public async Task Issue153() var q = await MiniExcel.QueryAsync(path, true); var rows = q.First() as IDictionary; - Assert.Equal(new[] { "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" } - , rows.Keys); + Assert.Equal( + [ + "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", + "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" + ], rows?.Keys); } /// @@ -1610,11 +1628,11 @@ public async Task Issue137() var q = await MiniExcel.QueryAsync(path); var rows = q.ToList(); var first = rows[0] as IDictionary; //![image](https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png) - Assert.Equal(new[] { "A", "B", "C", "D", "E", "F", "G", "H" }, first.Keys.ToArray()); + Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], first?.Keys.ToArray()); Assert.Equal(11, rows.Count); { var row = rows[0] as IDictionary; - Assert.Equal("比例", row["A"]); + Assert.Equal("比例", row!["A"]); Assert.Equal("商品", row["B"]); Assert.Equal("滿倉口數", row["C"]); Assert.Equal(" ", row["D"]); @@ -1625,7 +1643,7 @@ public async Task Issue137() } { var row = rows[1] as IDictionary; - Assert.Equal(1.0, row["A"]); + Assert.Equal(1.0, row!["A"]); Assert.Equal("MTX", row["B"]); Assert.Equal(10.0, row["C"]); Assert.Null(row["D"]); @@ -1636,7 +1654,7 @@ public async Task Issue137() } { var row = rows[2] as IDictionary; - Assert.Equal(0.95, row["A"]); + Assert.Equal(0.95, row!["A"]); } } @@ -1645,11 +1663,11 @@ public async Task Issue137() var q = await MiniExcel.QueryAsync(path, true); var rows = q.ToList(); var first = rows[0] as IDictionary; //![image](https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png) - Assert.Equal(new[] { "比例", "商品", "滿倉口數", "0", "1為港幣 0為台幣" }, first.Keys.ToArray()); + Assert.Equal(["比例", "商品", "滿倉口數", "0", "1為港幣 0為台幣"], first?.Keys.ToArray()); Assert.Equal(10, rows.Count); { var row = rows[0] as IDictionary; - Assert.Equal(1.0, row["比例"]); + Assert.Equal(1.0, row!["比例"]); Assert.Equal("MTX", row["商品"]); Assert.Equal(10.0, row["滿倉口數"]); Assert.Null(row["0"]); @@ -1658,7 +1676,7 @@ public async Task Issue137() { var row = rows[1] as IDictionary; - Assert.Equal(0.95, row["比例"]); + Assert.Equal(0.95, row!["比例"]); } } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index f8651cf7..05768c48 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -28,11 +28,11 @@ namespace MiniExcelLibs.Tests { public partial class MiniExcelIssueTests { - private readonly ITestOutputHelper output; + private readonly ITestOutputHelper _output; public MiniExcelIssueTests(ITestOutputHelper output) { - this.output = output; + _output = output; } /// @@ -307,12 +307,13 @@ public void TestIssue370() { var config = new OpenXmlConfiguration { - DynamicColumns = new DynamicExcelColumn[] { + DynamicColumns = + [ new DynamicExcelColumn("id"){Ignore=true}, new DynamicExcelColumn("name"){Index=1,Width=10}, new DynamicExcelColumn("createdate"){Index=0,Format="yyyy-MM-dd",Width=15}, - new DynamicExcelColumn("point"){Index=2,Name="Account Point"}, - } + new DynamicExcelColumn("point"){Index=2,Name="Account Point"} + ] }; var path = PathHelper.GetTempPath(); var json = JsonConvert.SerializeObject(new[] { new { id = 1, name = "Jack", createdate = new DateTime(2022, 04, 12), point = 123.456 } }, Formatting.Indented); @@ -333,12 +334,13 @@ public void TestIssue369() { var config = new OpenXmlConfiguration { - DynamicColumns = new DynamicExcelColumn[] { + DynamicColumns = + [ new DynamicExcelColumn("id"){Ignore=true}, new DynamicExcelColumn("name"){Index=1,Width=10}, new DynamicExcelColumn("createdate"){Index=0,Format="yyyy-MM-dd",Width=15}, - new DynamicExcelColumn("point"){Index=2,Name="Account Point"}, - } + new DynamicExcelColumn("point"){Index=2,Name="Account Point"} + ] }; var path = PathHelper.GetTempPath(); var value = new[] { new { id = 1, name = "Jack", createdate = new DateTime(2022, 04, 12), point = 123.456 } }; @@ -799,7 +801,7 @@ public void TestIssue331_2() }); var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - MiniExcelLibs.MiniExcel.SaveAs(path, data, configuration: config); + MiniExcel.SaveAs(path, data, configuration: config); Console.WriteLine(path); CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(cln); @@ -815,12 +817,12 @@ public void TestIssue331() { Number = x, Text = $"Number {x}", - DecimalNumber = (decimal)x / (decimal)2, - DoubleNumber = (double)x / (double)2 + DecimalNumber = x / 2m, + DoubleNumber = x / 2d }); var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - MiniExcelLibs.MiniExcel.SaveAs(path, data); + MiniExcel.SaveAs(path, data); Console.WriteLine(path); var rows = MiniExcel.Query(path, startCell: "A2").ToArray(); @@ -1009,10 +1011,11 @@ public void TestIssue316() { { var path = PathHelper.GetTempFilePath("csv"); - var value = new[] { + var value = new[] + { new{ amount=123_456.789M,createtime=DateTime.Parse("2018-01-31",CultureInfo.InvariantCulture)} }; - var config = new CsvConfiguration() + var config = new CsvConfiguration { Culture = new CultureInfo("fr-FR"), }; @@ -1022,11 +1025,11 @@ public void TestIssue316() { Assert.Throws(() => { - var config = new CsvConfiguration() + var conf = new CsvConfiguration() { Culture = new CultureInfo("en-US"), }; - MiniExcel.Query(path, configuration: config).ToList(); + MiniExcel.Query(path, configuration: conf).ToList(); }); } @@ -1115,7 +1118,8 @@ public void TestIssueI49RZH() // xlsx { var path = PathHelper.GetTempFilePath(); - var value = new TestIssueI49RZHDto[] { + var value = new [] + { new TestIssueI49RZHDto{ dd = DateTimeOffset.Parse("2022-01-22")}, new TestIssueI49RZHDto{ dd = null} }; @@ -1128,7 +1132,8 @@ public void TestIssueI49RZH() //TODO:CSV { var path = PathHelper.GetTempFilePath("csv"); - var value = new TestIssueI49RZHDto[] { + var value = new [] + { new TestIssueI49RZHDto{ dd = DateTimeOffset.Parse("2022-01-22")}, new TestIssueI49RZHDto{ dd = null} }; @@ -1154,10 +1159,11 @@ public void TestIssue312() //xlsx { var path = PathHelper.GetTempFilePath(); - var value = new TestIssue312Dto[] { - new TestIssue312Dto{ value = 12345.6789}, - new TestIssue312Dto{ value = null} - }; + TestIssue312Dto[] value = + [ + new() { value = 12345.6789}, + new() { value = null} + ]; MiniExcel.SaveAs(path, value); var rows = MiniExcel.Query(path).ToList(); @@ -1167,10 +1173,11 @@ public void TestIssue312() //TODO:CSV { var path = PathHelper.GetTempFilePath("csv"); - var value = new TestIssue312Dto[] { - new TestIssue312Dto{ value = 12345.6789}, - new TestIssue312Dto{ value = null} - }; + TestIssue312Dto[] value = + [ + new() { value = 12345.6789}, + new() { value = null} + ]; MiniExcel.SaveAs(path, value); var rows = MiniExcel.Query(path).ToList(); @@ -1335,8 +1342,10 @@ public void TestIssue294() public void TestIssue298() { var path = PathHelper.GetFile("/csv/TestIssue298.csv"); +#pragma warning disable CS0618 // Type or member is obsolete var dt = MiniExcel.QueryAsDataTable(path); - Assert.Equal(new[] { "ID", "Name", "Age" }, dt.Columns.Cast().Select(_ => _.ColumnName)); +#pragma warning restore CS0618 // Type or member is obsolete + Assert.Equal(["ID", "Name", "Age"], dt.Columns.Cast().Select(x => x.ColumnName)); } /// @@ -1525,14 +1534,17 @@ public void TestIssue283() var path = PathHelper.GetTempPath(); using (var cn = Db.GetConnection()) { - var sheets = new Dictionary { }; - sheets.Add("sheet01", cn.ExecuteReader(@"select 'v1' col1")); - sheets.Add("sheet02", cn.ExecuteReader(@"select 'v2' col1")); - MiniExcel.SaveAs(path, sheets); + var sheets = new Dictionary + { + { "sheet01", cn.ExecuteReader("select 'v1' col1") }, + { "sheet02", cn.ExecuteReader("select 'v2' col1") } + }; + var rows = MiniExcel.SaveAs(path, sheets); + Assert.Equal(2, rows.Length); } var sheetNames = MiniExcel.GetSheetNames(path); - Assert.Equal(new[] { "sheet01", "sheet02" }, sheetNames); + Assert.Equal(["sheet01", "sheet02"], sheetNames); } /// @@ -1871,24 +1883,30 @@ public void Issue253() var value = new[] { new { col1 = "世界你好" } }; var path = PathHelper.GetTempPath(extension: "csv"); MiniExcel.SaveAs(path, value); - var expected = @"col1 -世界你好 -"; + var expected = + """ + col1 + 世界你好 + + """; Assert.Equal(expected, File.ReadAllText(path)); } { - System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var value = new[] { new { col1 = "世界你好" } }; var path = PathHelper.GetTempPath(extension: "csv"); - var config = new CsvConfiguration() + var config = new CsvConfiguration { - StreamWriterFunc = (stream) => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) + StreamWriterFunc = stream => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) }; MiniExcel.SaveAs(path, value, excelType: ExcelType.CSV, configuration: config); - var expected = @"col1 -������� -"; + var expected = + """ + col1 + ������� + + """; Assert.Equal(expected, File.ReadAllText(path)); } @@ -1897,9 +1915,12 @@ public void Issue253() var value = cn.ExecuteReader(@"select '世界你好' col1"); var path = PathHelper.GetTempPath(extension: "csv"); MiniExcel.SaveAs(path, value); - var expected = @"col1 -世界你好 -"; + var expected = + """ + col1 + 世界你好 + + """; Assert.Equal(expected, File.ReadAllText(path)); } } @@ -1915,10 +1936,13 @@ public void Issue251() var reader = cn.ExecuteReader(@"select '""<>+-*//}{\\n' a,1234567890 b union all select 'Hello World',-1234567890"); var path = PathHelper.GetTempPath(extension: "csv"); MiniExcel.SaveAs(path, reader); - var expected = @"a,b -""""""<>+-*//}{\\n"",1234567890 -""Hello World"",-1234567890 -"; + var expected = + """" + a,b + """<>+-*//}{\\n",1234567890 + "Hello World",-1234567890 + + """"; Assert.Equal(expected, File.ReadAllText(path)); } } @@ -1946,7 +1970,8 @@ public void Issue242() public void Issue243() { var path = PathHelper.GetTempPath("csv"); - var value = new[] { + var value = new[] + { new { Name ="Jack",Age=25,InDate=new DateTime(2021,01,03)}, new { Name ="Henry",Age=36,InDate=new DateTime(2020,05,03)}, }; @@ -2066,7 +2091,7 @@ public void Issue132() MiniExcel.SaveAs(path, value); } } - + /// /// Support SaveAs by DataSet #235 /// @@ -2079,13 +2104,14 @@ public void Issue235() DataSet sheets = dataSet; var users = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { Name = "Jack", Age = 25 }, new { Name = "Mike", Age = 44 } })); users.TableName = "users"; - var department = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } })); ; + var department = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } })); department.TableName = "department"; sheets.Tables.Add(users); sheets.Tables.Add(department); - MiniExcel.SaveAs(path, sheets); - + var rowsWritten = MiniExcel.SaveAs(path, sheets); + Assert.Equal(2, rowsWritten.Length); + Assert.Equal(2, rowsWritten[0]); var sheetNames = MiniExcel.GetSheetNames(path); Assert.Equal("users", sheetNames[0]); @@ -2197,7 +2223,7 @@ public void Issue230() for (int i = 0; i < reader.FieldCount; i++) { var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; - output.WriteLine(result); + _output.WriteLine(result); } } } @@ -2213,7 +2239,7 @@ public void Issue230() for (int i = 0; i < reader.FieldCount; i++) { var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; - output.WriteLine(result); + _output.WriteLine(result); } } } @@ -2396,39 +2422,39 @@ public void Issue147() var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); var rows = MiniExcel.Query(path, useHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToList(); - Assert.Equal(new[] { "C", "D", "E" }, (rows[0] as IDictionary).Keys); - Assert.Equal(new[] { "Column1", "Column2", "Column3" }, new[] { rows[0].C as string, rows[0].D as string, rows[0].E as string }); - Assert.Equal(new[] { "C4", "D4", "E4" }, new[] { rows[1].C as string, rows[1].D as string, rows[1].E as string }); - Assert.Equal(new[] { "C9", "D9", "E9" }, new[] { rows[6].C as string, rows[6].D as string, rows[6].E as string }); - Assert.Equal(new[] { "C12", "D12", "E12" }, new[] { rows[9].C as string, rows[9].D as string, rows[9].E as string }); - Assert.Equal(new[] { "C13", "D13", "E13" }, new[] { rows[10].C as string, rows[10].D as string, rows[10].E as string }); + Assert.Equal(["C", "D", "E"], (rows[0] as IDictionary)?.Keys); + Assert.Equal(["Column1", "Column2", "Column3"], new[] { rows[0].C as string, rows[0].D as string, rows[0].E as string }); + Assert.Equal(["C4", "D4", "E4"], new[] { rows[1].C as string, rows[1].D as string, rows[1].E as string }); + Assert.Equal(["C9", "D9", "E9"], new[] { rows[6].C as string, rows[6].D as string, rows[6].E as string }); + Assert.Equal(["C12", "D12", "E12"], new[] { rows[9].C as string, rows[9].D as string, rows[9].E as string }); + Assert.Equal(["C13", "D13", "E13"], new[] { rows[10].C as string, rows[10].D as string, rows[10].E as string }); foreach (var i in new[] { 4, 5, 7, 8 }) - Assert.Equal(expected: new[] { default, default, default(string) }, new[] { rows[i].C as string, rows[i].D as string, rows[i].E as string }); + Assert.Equal(expected: [default, default, default(string)], new[] { rows[i].C as string, rows[i].D as string, rows[i].E as string }); Assert.Equal(11, rows.Count); var columns = MiniExcel.GetColumns(path, startCell: "C3"); - Assert.Equal(new[] { "C", "D", "E" }, columns); + Assert.Equal(["C", "D", "E"], columns); } { var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); var rows = MiniExcel.Query(path, useHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToList(); - Assert.Equal(new[] { "Column1", "Column2", "Column3" }, (rows[0] as IDictionary).Keys); - Assert.Equal(new[] { "C4", "D4", "E4" }, new[] { rows[0].Column1 as string, rows[0].Column2 as string, rows[0].Column3 as string }); - Assert.Equal(new[] { "C9", "D9", "E9" }, new[] { rows[5].Column1 as string, rows[5].Column2 as string, rows[5].Column3 as string }); - Assert.Equal(new[] { "C12", "D12", "E12" }, new[] { rows[8].Column1 as string, rows[8].Column2 as string, rows[8].Column3 as string }); - Assert.Equal(new[] { "C13", "D13", "E13" }, new[] { rows[9].Column1 as string, rows[9].Column2 as string, rows[9].Column3 as string }); + Assert.Equal(["Column1", "Column2", "Column3"], (rows[0] as IDictionary)?.Keys); + Assert.Equal(["C4", "D4", "E4"], new[] { rows[0].Column1 as string, rows[0].Column2 as string, rows[0].Column3 as string }); + Assert.Equal(["C9", "D9", "E9"], new[] { rows[5].Column1 as string, rows[5].Column2 as string, rows[5].Column3 as string }); + Assert.Equal(["C12", "D12", "E12"], new[] { rows[8].Column1 as string, rows[8].Column2 as string, rows[8].Column3 as string }); + Assert.Equal(["C13", "D13", "E13"], new[] { rows[9].Column1 as string, rows[9].Column2 as string, rows[9].Column3 as string }); foreach (var i in new[] { 3, 4, 6, 7 }) - Assert.Equal(new[] { default, default, default(string) }, new[] { rows[i].Column1 as string, rows[i].Column2 as string, rows[i].Column3 as string }); + Assert.Equal([default, default, default(string)], new[] { rows[i].Column1 as string, rows[i].Column2 as string, rows[i].Column3 as string }); Assert.Equal(10, rows.Count); var columns = MiniExcel.GetColumns(path, useHeaderRow: true, startCell: "C3"); - Assert.Equal(new[] { "Column1", "Column2", "Column3" }, columns); + Assert.Equal(["Column1", "Column2", "Column3"], columns); } } @@ -2534,9 +2560,9 @@ group s by s.PRT_ID into g select new { PRT_ID = g.Key, - Apr = g.Sum(_ => (double?)_.Apr), - May = g.Sum(_ => (double?)_.May), - Jun = g.Sum(_ => (double?)_.Jun), + Apr = g.Sum(x => (double?)x.Apr), + May = g.Sum(x => (double?)x.May), + Jun = g.Sum(x => (double?)x.Jun), } ).ToList(); Assert.Equal(91843.25, result[0].Jun); @@ -2683,7 +2709,7 @@ public void Issue212() } /// - /// Version <= v0.13.1 Template merge row list rendering has no merge + /// Version <= v0.13.1 Template merge row list rendering has no merge /// https://github.com/shps951023/MiniExcel/issues/207 /// [Fact] @@ -2691,7 +2717,7 @@ public void Issue207() { { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var tempaltePath = @"../../../../../samples/xlsx/TestIssue207_2.xlsx"; + var tempaltePath = "../../../../../samples/xlsx/TestIssue207_2.xlsx"; var value = new { @@ -2966,7 +2992,18 @@ public void Issue142() { { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - MiniExcel.SaveAs(path, new Issue142VO[] { new Issue142VO { MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", MyProperty7 = "MyProperty7" } }); + Issue142VO[] values = + [ + new() + { + MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", + MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", + MyProperty7 = "MyProperty7" + } + ]; + var rowsWritten = MiniExcel.SaveAs(path, values); + Assert.Single(rowsWritten); + Assert.Equal(1, rowsWritten[0]); { var rows = MiniExcel.Query(path).ToList(); @@ -2991,7 +3028,6 @@ public void Issue142() { var rows = MiniExcel.Query(path).ToList(); - Assert.Equal("MyProperty4", rows[0].MyProperty4); Assert.Equal("MyProperty1", rows[0].MyProperty1); //note Assert.Equal("MyProperty5", rows[0].MyProperty5); @@ -3007,16 +3043,30 @@ public void Issue142() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); - MiniExcel.SaveAs(path, new Issue142VO[] { new Issue142VO { MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", MyProperty7 = "MyProperty7" } }); - var expected = @"MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 -MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 -"; + Issue142VO[] values = + [ + new() + { + MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", + MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", + MyProperty7 = "MyProperty7" + } + ]; + var rowsWritten = MiniExcel.SaveAs(path, values); + Assert.Single(rowsWritten); + Assert.Equal(1, rowsWritten[0]); + + var expected = + """ + MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + + """; Assert.Equal(expected, File.ReadAllText(path)); { var rows = MiniExcel.Query(path).ToList(); - Assert.Equal("MyProperty4", rows[0].MyProperty4); Assert.Equal("MyProperty1", rows[0].MyProperty1); //note Assert.Equal("MyProperty5", rows[0].MyProperty5); @@ -3031,7 +3081,10 @@ public void Issue142() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); - var input = new Issue142VoDuplicateColumnName[] { new Issue142VoDuplicateColumnName { } }; + Issue142VoDuplicateColumnName[] input = + [ + new() { MyProperty1 = 0, MyProperty2 = 0, MyProperty3 = 0, MyProperty4 = 0 } + ]; Assert.Throws(() => MiniExcel.SaveAs(path, input)); } } @@ -3222,7 +3275,7 @@ public void Issue149() var rows = MiniExcel.Query(path, true).Select(s => (string)s.Test).ToList(); for (int i = 0; i < chars.Length; i++) { - output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); + _output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); if (i == 13 || i == 9 || i == 10) continue; Assert.Equal(chars[i], rows[i]); @@ -3234,10 +3287,10 @@ public void Issue149() var input = chars.Select(s => new { Test = s.ToString() }); MiniExcel.SaveAs(path, input); - var rows = MiniExcel.Query(path).Select(s => (string)s.Test).ToList(); + var rows = MiniExcel.Query(path).Select(s => s.Test).ToList(); for (int i = 0; i < chars.Length; i++) { - output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); + _output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); if (i == 13 || i == 9 || i == 10) continue; Assert.Equal(chars[i], rows[i]); @@ -3258,8 +3311,11 @@ public void Issue153() { var path = @"../../../../../samples/xlsx/TestIssue153.xlsx"; var rows = MiniExcel.Query(path, true).First() as IDictionary; - Assert.Equal(new[] { "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" } - , rows.Keys); + Assert.Equal( + [ + "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", + "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" + ], rows?.Keys); } /// @@ -3268,16 +3324,16 @@ public void Issue153() [Fact] public void Issue137() { - var path = @"../../../../../samples/xlsx/TestIssue137.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue137.xlsx"; { var rows = MiniExcel.Query(path).ToList(); var first = rows[0] as IDictionary; //![image](https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png) - Assert.Equal(new[] { "A", "B", "C", "D", "E", "F", "G", "H" }, first.Keys.ToArray()); + Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], first?.Keys.ToArray()); Assert.Equal(11, rows.Count); { var row = rows[0] as IDictionary; - Assert.Equal("比例", row["A"]); + Assert.Equal("比例", row!["A"]); Assert.Equal("商品", row["B"]); Assert.Equal("滿倉口數", row["C"]); Assert.Equal(" ", row["D"]); @@ -3288,7 +3344,7 @@ public void Issue137() } { var row = rows[1] as IDictionary; - Assert.Equal(1.0, row["A"]); + Assert.Equal(1.0, row!["A"]); Assert.Equal("MTX", row["B"]); Assert.Equal(10.0, row["C"]); Assert.Null(row["D"]); @@ -3299,7 +3355,7 @@ public void Issue137() } { var row = rows[2] as IDictionary; - Assert.Equal(0.95, row["A"]); + Assert.Equal(0.95, row!["A"]); } } @@ -3307,11 +3363,11 @@ public void Issue137() { var rows = MiniExcel.Query(path, true).ToList(); var first = rows[0] as IDictionary; //![image](https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png) - Assert.Equal(new[] { "比例", "商品", "滿倉口數", "0", "1為港幣 0為台幣" }, first.Keys.ToArray()); + Assert.Equal(["比例", "商品", "滿倉口數", "0", "1為港幣 0為台幣"], first?.Keys.ToArray()); Assert.Equal(10, rows.Count); { var row = rows[0] as IDictionary; - Assert.Equal(1.0, row["比例"]); + Assert.Equal(1.0, row!["比例"]); Assert.Equal("MTX", row["商品"]); Assert.Equal(10.0, row["滿倉口數"]); Assert.Null(row["0"]); @@ -3320,7 +3376,7 @@ public void Issue137() { var row = rows[1] as IDictionary; - Assert.Equal(0.95, row["比例"]); + Assert.Equal(0.95, row!["比例"]); } } @@ -3733,13 +3789,13 @@ public void Issue632_1() var config = new OpenXmlConfiguration { TableStyles = TableStyles.None, - DynamicColumns = new DynamicExcelColumn[] - { + DynamicColumns = + [ //new DynamicExcelColumn("Time") { Index = 0, Width = 20, Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern + " " + CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern }, //new DynamicExcelColumn("Time") { Index = 0, Width = 20, Format = CultureInfo.InvariantCulture.DateTimeFormat.ShortDatePattern + " " + CultureInfo.InvariantCulture.DateTimeFormat.LongTimePattern }, //new DynamicExcelColumn("Time") { Index = 0, Width = 20 }, - new DynamicExcelColumn("Time") { Index = 0, Width = 20, Format = "d.MM.yyyy" }, - } + new DynamicExcelColumn("Time") { Index = 0, Width = 20, Format = "d.MM.yyyy" } + ] }; var path = Path.Combine( @@ -3772,10 +3828,12 @@ static IEnumerable GetTestData() using var memoryStream = new MemoryStream(); var testData = GetTestData(); - memoryStream.SaveAs(testData, configuration: new OpenXmlConfiguration + var rowsWritten = memoryStream.SaveAs(testData, configuration: new OpenXmlConfiguration { FastMode = true, }); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); memoryStream.Position = 0; From 8e6b2a8f193b9bc3e78d22710f08d6437ffc6427 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Sat, 15 Mar 2025 15:55:45 +0100 Subject: [PATCH 4/4] Added active tab functionality Adde property Active in classes SheetInfo and SheetRecord to indicate wheter the worksheet referenced represents the active tab --- samples/xlsx/TestIssue732_1.xlsx | Bin 0 -> 7252 bytes samples/xlsx/TestIssue732_2.xlsx | Bin 0 -> 7253 bytes samples/xlsx/TestIssue732_3.xlsx | Bin 0 -> 6132 bytes .../OpenXml/ExcelOpenXmlSheetReader.cs | 43 ++++++++++++++---- src/MiniExcel/OpenXml/SheetInfo.cs | 7 ++- src/MiniExcel/OpenXml/SheetRecord.cs | 9 ++-- tests/MiniExcelTests/MiniExcelIssueTests.cs | 27 +++++++++++ 7 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 samples/xlsx/TestIssue732_1.xlsx create mode 100644 samples/xlsx/TestIssue732_2.xlsx create mode 100644 samples/xlsx/TestIssue732_3.xlsx diff --git a/samples/xlsx/TestIssue732_1.xlsx b/samples/xlsx/TestIssue732_1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..fdf21e4de3dc9c862a93827fd096b2ce239f471e GIT binary patch literal 7252 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 Mh0V2($R9raA1;JN!~g&Q literal 0 HcmV?d00001 diff --git a/samples/xlsx/TestIssue732_2.xlsx b/samples/xlsx/TestIssue732_2.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0c68ca1bf17a2ff1af70194e03bde1e22fdf29c7 GIT binary patch literal 7253 zcmbVRby$>Z(`V^!SVFoxg{1_fySq!eJC>F%=?1}FKt!aaC6!h{QbHspmXZcP@VwV6 z$L~Amk8hr9`#GO zc1qNmPnzS#1;g~DgQJb#65?9OAlT`BR~hm-omF7$?W>^+)HkoZ31q^a)pXYR0dN{y z8y4bAJ0Gb?UF0KStB*67!O2EpooH5X%?1a~95W8reb5<&=;GG#>HF0A35q+^sWmJH zrdK-+EA|GgUkb22Uump%R9|JOuK;xH+FE?`l-lPT-B3gB>Jgye3kV;q61S#h*8pfdcT0^vY&~h`(R#M=W!xM6#cv*R%S-6YyBdWwo*UdgG zGV|h$;ToGXm6ns?$Ri0?HNpaTh!QCq_k!2j2{Rc!>7=RCU%1!Exi?<-!)^L8icb;qVrG<9MOh?gJXK_*U3Dkcm>saa=u`Ep-ldF2j;vxPEFK44E;{{s&wl# zYOp-nJ|{wTLz)B`z24_;nunO$$=qZaHyN~ob$iF@j^#s`rnKpZAvk(Vm2(u&7HXVS@` zX_&NX_@yK+sKBi|nRvn9K)Pb<<_Rf89;<$PL*SV|3Z3Q2w`&j?Q=0o^eCF3;VAQq- ztj8Sm-FYex>xY;D!^DW%oMNaFUWEP z1JspJP*c=-X;9$d;9euZ!KwcvhOqv^kf)cwla1#cf_C*y)aJPf!AsR|t{$bPYnm%Y zkZ6^)E87vG+0RAffnuME)6PGQGV9z1siYALR0QfjyRg1IEA&iub^7cQ)PVm$Ov5=l zRqH`PB#B1O%Y*^TIM_yAGLVE+$y)Yps#lz&!@kZTjX>l2@#`0pwCX8O;*=aK)g;Zw zQ_(F|wPhHFR~0cVEz0BHY3HU6gHo5fm@%2csFhdfGnIGh1TMqBc74q2&q#7#%y5YmOX(%5$#f8k z061J8wS;#o%;1_rV=tAwo-0r!js`?R!`d()d^4ff$uOF99!+M2Gw{Pcp|4RZ*cT;* zYh=ons-76#xkM1hs+aIgmR0y44LpqzV{G`*-dTGrUDz(zq zWKuneEw6JB0L_p2tTYE-l$QeV8Zn?jT0n36JUBin5M2|Nmi-Drqk=yWV1EjT_c>~< zb2Ru)-D#S@ZZb6LVk(J|f&7Qq~S5n2L{!{9tLN?gkhKGa@(+FRjPW(j#x@dt*RT2%wC01 z-_pWR{wZsdKCes8ajAKhTWKWq_SvSB1PGiK8XVOrJZ>$K+oovfpkia_n|}nbyE7#>-;Dq1p7ujm{T<$CO5ljr6h;ayK0gT4X5UTisp^ z>x!N*NxskPRu6i~HFw&z)UAb3z4`^)w+Y*JD5rhjROo#sz|NgWv%AEV`R=X}W`ljI zDQo3E$#VoSX|Wee%+*pTz?|Fhj;r0+ot&g$JL62Ye8#|+l0`bmKuSx6oWK-gB?b&>u)b<71J}m{(Vk`Cf0^dF z1Wy0N)Ee#j!Y)iwiI!&zt>2nBS6-va)?pYs>AXR_b6D-@n$y;;I|Tlf4G8{nn3tW6 zvklj;XP!Gw=+iTE&Ev)mSt@&T)j`(XN$)To?emFJSY@sSXPE@S)H%Yb$)w{tfd&A- zMawZOL3?uU>npx=j_nb6s^qq>nDGQ+EcvrPB2-Zms=Us2_Rapt9Xm`wADW$&`g%FZ z!LRAYEj|lc91dT}`2)Y&+(NlQTAqNd{{s4|yx!5E0#U%{rAIpJ)sqqQq$h zn+>>Z+}8LSm|g1(R3m;jEca{VF24doNGNO;N7nDb=TkT;uQNAV;urA-IFp#AvDx%| z_}Kdz(aCaWy*>8XnETk;Zj43mNfTB=cYFgwtr>obqM1osWDXnpML%v&o7hzH{Qdy# zs!p36!)>4;S{uHu(k?2tZ^3G`F)mCl6Nns+KBQ?>BX}fWYF_k%qD8|_FNdZN8Kd8q z7#_g#W_)tSp$ctGze~P)5167PbaMQ3}F2pHyskp#ux>fpwy(XlaG+~v&js8l@jFuG~;a0U60q`H?^Fc8%;k75j?L*{|Hv_hf zQ_K;7fs{I{p%p$svV<@)wPfH((!OFL%g2;F1!NngUC>bF@^_s=HP#!x7j{V%d(Bna zU&1nG18do1171}o$Cy0VG~#^g?}K(}zp~%S#w^A|k_ zK00VGko^qn;fUcXM+$~oy$`{Z*~W~6pmX~Bz*eS?q^f$ z&6LBBHl{Ik5fj7eYe#1QW!1n+RHGs%R;8=V&e9v3uYrhFfdX$xPeydqemb!65)Q*B zPn$UF?xl4ZHYpaPiwiyM8}H0#=X_0^1w1lkU>^iNVUQcQ19xe9UFzD|Nq5)=GVa&Y z@io!}q#5nA`VFlmk)$%BV^dCby)<@2u<pBuKjl8|*Y z7Klk~HcO89+8Ck+{rRhv=n@wxNx!X%Y3Tot%;vo-kL|iDuD6-p4y(rX+O&6^tzF}< z*r9b3nD2vntCw-S5j1?kPD>cgz#=kGy+rdP2gDq{ON)%3$G4g%==y>bO0}nmx056A zV3jsMq2J=2HFEmU0_|0R=lO{V35gOt13bM^9#(dW4q!m5!~3 zb!~V?Z-N9*Y~ceh1$NFKoV<#BkFTF=N+0-%CM$N^=+otIWtmG99wnGa=w>L-kxBDO zvX2WQ=|N>$OS!sBb02Vj&KwI}k=fnaXRM=ZOSL7#T=hwpoK+VYJ>(Y#r*9M_{Ujf! z@6tZ~qQmOaHMqlIkSV>S`<=s*P1|7zsp<65SGSyiD@Te|V|}PNppSM#nk4FxjE_^# zj^5h!3G(YtA6rUo$R0*#v8?Iiyl4N&ZWg(bR(I69yHDQX?!z6i6vndb7JJ1isf@9a z7~-H>{npy9p~WG=Z21lR0bawq_gFJuM zjDVYwJ^|D-(-igP06k$YyMfkisE{-*YIp>w_{~rVXOz=)s|JzlFR8q_^IT(IP%FhXx>jB6nAd9$HLP!_!w5d{nQuOT*4J zfYyuCLjatRCP?$K8fIpT2E5MMXaz^gv~{|2ngVZfnkv()LAiB)IbYFk&V$g;b{vT} z>KNd@6XoS#H9NZ=RPW$VbnsY37?uOPpWRU35UXwt>N9Vvn4nUHd|v_PYch(+wO=wt ziju!7q#k^`Q3~GV1CHuUol*GB6nl5(mwlqld2$w=FKl_hMi8GIQy{A5bTN3B;m|RH zyz2!0tDgjU6nJ@JYV7_tmIHV-t7tWL`?Sm>5EC}>t#J5O8r8Cnz`cvn^T}|Ewp|+} zyBoN1{F?5A0k5%v1pxb45`~5s@*}+VZzTF>H^P(!Z6XS}(0Q&Jj&l)d{30(ZZD$N( zn}AWRGw341DxA-oq!v7FmYtxuEv%4tC%$prSS_!pRGcdvGEH335m0KT9$`O?Xk)p} z>PCH*5gvmOU;Rk*`p=P8Y76M83x!G-?aPgMK7d6+Ou&=E?&%V!%v(ftvc+>iwXi>!Df^@cOKiHUkl*MJM?? z+-P2gj>P={F>%UwF|=wRAQZ1detDHDKPGo-koU2+Nz#;JOuB}T4ULO94Dw|1Sa!z5dqu9!rwD#b1Fb~~rR*ezUgWgeg76`U3i&#) z9g1rNE^tD;k5J;|8n@ltMfr+RR%^ABuRZz`rr#?am9% z+SN+k!`01`%gWWm=8g)%nkGs;x6^}Dz4SI;dqY9v8DVu&RBS}HI9Hzb?UgxxdSA1j zi}gm^lV9h>PV@6GGX|W@GP;OgX*Cq$3&hC78BR5i_ocU4AMHmWVm}>~F>uUIMF8_nW-fJ`XGV*X+G+aWmd)_`7Juxz^yh-bQNiwmbdj&J_24v@G1*?t)eDY|OQr zn(#gBl8vmk@}n-K;CVK~kg8In)P|2Eu4x7Y34VAhM0`?NEV(V^l&uK%tt<1={e#bP zL?cuZX;cGQ)(|oRm4xjcy^uAC>1O89N5B%6kWdPKzu4xzTpE?a5RQg5*S|>|iP-#9 z12KP)Q72$q4o_=Su=k`rbKiRH6qPZRcZ?+rj94n6@&zeow6~A*2Ke%o=qONkw~K+& zmLtIh7Y{_I*VLeaSw$HJWC5eilzK*F(n1sWf$b;z0jbnPP`{}7OC0C)YqSMh6%pYf z7sHn07_O9O5(g*GNp z+NUXtjQ~&A4=VBCK)Ud~q-d%~suN<@Qm=4l@f@1D*jR2i71&H1uGbKdLf62uIb~$= z8ZvvU&$KZeQp^A#o}JoJsEp77GBdi2l_g}wNhjl~S?I3GpETN?v$Fz76W0E$G5eI@ zoHj`4{NVe;Yb9*!CD_@bD@~wtX|V15FJ}8X1s9riyXu46$3KGvB@G%o>dy?FPLvAb~i^?wS zi`iT$-x(aUMY&`n@1{2 zp8y+HJJxVw644$eI=;L&)-mgm20ID(?zXxmGa-m=kt)uP4_y?f9t1q%ThXIUqw)Em zs9YLU25Q5{vWY_XU$g-?4=uscu}LwxDEQ12;|&Y1hSmx0!v8dvKX>oe38Vj-OLyTnl5%zNvT^Y; z)Ao0>@ie&$f1l2fO9VG=+Xfr%G{Q14#xZ!eG`fZ*?RS&)2WXvCv9q=gYLe6G$Cd(Y% zGJ5Q!8(u7+OGYp2VT0&>=73&e-&lf_vgH?~!plG*4-^{18%+)Tu+`$x)kEAwZW0;Y zWsX;mx7$kfUexGSPUpawpNZg6|c7OCLSY$;? zO5_DL^*ZUiGbqAbGBL0Op5Q|xZPDPX#SFu~g%P`h7l-+_@3b3?^g7kkKZ3|k**1bB*@)-3&S~yeA5GnS{K@KZi5QZZHi4*)x0phsHAMV zd>3LtF%j~zCvy@m&>gutuS*WbhyUhxI!s0Qtfk3u zUk?Ytr^2VPQ8jk)5Nm|0FOLhnTpgI6a&gLk`HKt3#rr>m+@|gb@*T_p@Pu%`PfqU7 z!|zT`{w{x-jIa|EE&^ zsePYL{~il}qI=p_|1AxF8n{n1cdYwcG>HGk!G9{=NBH0W*$?=CvXZ(I(k(pT;4p75 Nl-u4GM0)q>e*oS_FxUV9 literal 0 HcmV?d00001 diff --git a/samples/xlsx/TestIssue732_3.xlsx b/samples/xlsx/TestIssue732_3.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b140fe6593542bb6b091c6cf63aba316f4a19544 GIT binary patch literal 6132 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 Query(bool useHeaderRow, string sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheetRecord.Path}" || w.FullName == $"/xl/{sheetRecord.Path}" || w.FullName == sheetRecord.Path || sheetRecord.Path == $"/{w.FullName}"); } - else if (sheets.Count() > 1) + else if (sheets.Length > 1) { SetWorkbookRels(_archive.entries); var s = _sheetRecords[0]; @@ -534,24 +534,49 @@ private void SetWorkbookRels(ReadOnlyCollection entries) _sheetRecords = GetWorkbookRels(entries); } - internal IEnumerable ReadWorkbook(ReadOnlyCollection entries) + internal static IEnumerable ReadWorkbook(ReadOnlyCollection entries) { using (var stream = entries.Single(w => w.FullName == "xl/workbook.xml").Open()) - using (XmlReader reader = XmlReader.Create(stream, _xmlSettings)) + using (var reader = XmlReader.Create(stream, _xmlSettings)) { if (!XmlReaderHelper.IsStartElement(reader, "workbook", _ns)) yield break; if (!XmlReaderHelper.ReadFirstContent(reader)) yield break; - + + var activeSheetIndex = 0; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "sheets", _ns)) + if (XmlReaderHelper.IsStartElement(reader, "bookViews", _ns)) + { + if (!XmlReaderHelper.ReadFirstContent(reader)) + continue; + + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "workbookView", _ns)) + { + var activeSheet = reader.GetAttribute("activeTab"); + if(int.TryParse(activeSheet, out var index)) + { + activeSheetIndex = index; + } + + reader.Skip(); + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; + } + } + } + else if (XmlReaderHelper.IsStartElement(reader, "sheets", _ns)) { if (!XmlReaderHelper.ReadFirstContent(reader)) continue; + var sheetCount = 0; while (!reader.EOF) { if (XmlReaderHelper.IsStartElement(reader, "sheet", _ns)) @@ -560,8 +585,10 @@ internal IEnumerable ReadWorkbook(ReadOnlyCollection GetWorkbookRels(ReadOnlyCollection e var sheetRecords = ReadWorkbook(entries).ToList(); using (var stream = entries.Single(w => w.FullName == "xl/_rels/workbook.xml.rels").Open()) - using (XmlReader reader = XmlReader.Create(stream, _xmlSettings)) + using (var reader = XmlReader.Create(stream, _xmlSettings)) { if (!XmlReaderHelper.IsStartElement(reader, "Relationships", "http://schemas.openxmlformats.org/package/2006/relationships")) return null; diff --git a/src/MiniExcel/OpenXml/SheetInfo.cs b/src/MiniExcel/OpenXml/SheetInfo.cs index b0fddb32..11606ddd 100644 --- a/src/MiniExcel/OpenXml/SheetInfo.cs +++ b/src/MiniExcel/OpenXml/SheetInfo.cs @@ -2,12 +2,13 @@ { public class SheetInfo { - public SheetInfo(uint id, uint index, string name, SheetState sheetState) + public SheetInfo(uint id, uint index, string name, SheetState sheetState, bool active) { Id = id; Index = index; Name = name; State = sheetState; + Active = active; } /// @@ -26,6 +27,10 @@ public SheetInfo(uint id, uint index, string name, SheetState sheetState) /// Sheet visibility state /// public SheetState State { get; } + /// + /// Indicates whether the sheet is active + /// + public bool Active { get; } } public enum SheetState { Visible, Hidden, VeryHidden } diff --git a/src/MiniExcel/OpenXml/SheetRecord.cs b/src/MiniExcel/OpenXml/SheetRecord.cs index a33b4ae7..e301b932 100644 --- a/src/MiniExcel/OpenXml/SheetRecord.cs +++ b/src/MiniExcel/OpenXml/SheetRecord.cs @@ -4,12 +4,13 @@ namespace MiniExcelLibs.OpenXml { internal sealed class SheetRecord { - public SheetRecord(string name, string state, uint id, string rid) + public SheetRecord(string name, string state, uint id, string rid, bool active) { Name = name; State = state; Id = id; Rid = rid; + Active = active; } public string Name { get; } @@ -22,15 +23,17 @@ public SheetRecord(string name, string state, uint id, string rid) public string Path { get; set; } + public bool Active { get; } + public SheetInfo ToSheetInfo(uint index) { if (string.IsNullOrEmpty(State)) { - return new SheetInfo(Id, index, Name, SheetState.Visible); + return new SheetInfo(Id, index, Name, SheetState.Visible, Active); } if (Enum.TryParse(State, true, out SheetState stateEnum)) { - return new SheetInfo(Id, index, Name, stateEnum); + return new SheetInfo(Id, index, Name, stateEnum, Active); } throw new ArgumentException($"Unable to parse sheet state. Sheet name: {Name}"); } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 05768c48..76a7a1a2 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -3912,5 +3912,32 @@ public void Issue_710() } } } + + [Fact] + public void Issue_732_First_Sheet_Active() + { + var path = "../../../../../samples/xlsx/TestIssue732_1.xlsx"; + var info = MiniExcel.GetSheetInformations(path); + + Assert.Equal(0u, info.SingleOrDefault(x => x.Active)?.Index); + } + + [Fact] + public void Issue_732_Second_Sheet_Active() + { + var path = "../../../../../samples/xlsx/TestIssue732_2.xlsx"; + var info = MiniExcel.GetSheetInformations(path); + + Assert.Equal(1u, info.SingleOrDefault(x => x.Active)?.Index); + } + + [Fact] + public void Issue_732_Only_One_Sheet() + { + var path = "../../../../../samples/xlsx/TestIssue732_3.xlsx"; + var info = MiniExcel.GetSheetInformations(path); + + Assert.Equal(0u, info.SingleOrDefault(x => x.Active)?.Index); + } } } \ No newline at end of file