diff --git a/samples/xlsx/TestIssue732_1.xlsx b/samples/xlsx/TestIssue732_1.xlsx new file mode 100644 index 00000000..fdf21e4d Binary files /dev/null and b/samples/xlsx/TestIssue732_1.xlsx differ diff --git a/samples/xlsx/TestIssue732_2.xlsx b/samples/xlsx/TestIssue732_2.xlsx new file mode 100644 index 00000000..0c68ca1b Binary files /dev/null and b/samples/xlsx/TestIssue732_2.xlsx differ diff --git a/samples/xlsx/TestIssue732_3.xlsx b/samples/xlsx/TestIssue732_3.xlsx new file mode 100644 index 00000000..b140fe65 Binary files /dev/null and b/samples/xlsx/TestIssue732_3.xlsx differ 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..6d026b66 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, cancellationToken); } 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, 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)) + 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, 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)) + 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/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index 67537af2..3ddd6c9b 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -29,7 +29,7 @@ internal class ExcelOpenXmlSheetReader : IExcelReader { IgnoreComments = true, IgnoreWhitespace = true, - XmlResolver = null, + XmlResolver = null }; public ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration) @@ -68,7 +68,7 @@ public IEnumerable> 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/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index 88ab2411..eda438a1 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -17,83 +17,101 @@ 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 rowsWritten = new List(); - var sheets = GetSheets(); + 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); + rowsWritten.Add(rows); + } - foreach (var sheet in sheets) + 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(); + + 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 }); + } + 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 +121,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 +137,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 +157,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,8 +168,10 @@ 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) { + cancellationToken.ThrowIfCancellationRequested(); + #if NETSTANDARD2_0_OR_GREATER || NET IMiniExcelWriteAdapter writeAdapter = null; if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) @@ -167,7 +192,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object va if (props == null) { await WriteEmptySheetAsync(writer); - return; + return 0; } var maxColumnIndex = props.Count; int maxRowIndex; @@ -200,7 +225,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object va } else { - await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props)); + await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken); } //header @@ -208,7 +233,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object va var currentRowIndex = 0; if (_printHeader) { - await PrintHeaderAsync(writer, props); + await PrintHeaderAsync(writer, props, cancellationToken); currentRowIndex++; } @@ -216,9 +241,12 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object va { 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); @@ -229,9 +257,12 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object va { 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); @@ -241,7 +272,7 @@ private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object va maxRowIndex = currentRowIndex; - await writer.WriteAsync(WorksheetXml.Drawing(currentSheetIndex)); + await writer.WriteAsync(WorksheetXml.Drawing(_currentSheetIndex)); await writer.WriteAsync(WorksheetXml.EndSheetData); if (_configuration.AutoFilter) @@ -251,39 +282,46 @@ 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); } 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); @@ -291,14 +329,14 @@ private async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IE } await writer.WriteAsync(WorksheetXml.Column(column.Index, column.Width)); } + if (!hasWrittenStart) - { return; - } + 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; @@ -306,6 +344,8 @@ private async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List s.FullName == ExcelFileNames.ContentTypes); if (contentTypesZipEntry == null) { 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) { + cancellationToken.ThrowIfCancellationRequested(); + var partName = $"/{p.Key}"; if (!partNames.Contains(partName)) { @@ -502,6 +551,7 @@ private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToke typesElement.Add(newElement); } } + stream.Position = 0; doc.Save(stream); } @@ -509,17 +559,22 @@ 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 (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)); } 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/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index 2c5639ee..d90d3af3 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); } @@ -326,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) @@ -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..6bbc4f71 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -26,50 +26,51 @@ 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) { - 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); + _currentSheetIndex = sheet.Item1.SheetIdx; + 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,29 +89,29 @@ public void Insert(bool overwriteSheet = false) } GenerateStylesXml();//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 - + 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); - CreateSheetXml(_value, insertSheetDto.Path); + rowsWritten = CreateSheetXml(_value, insertSheetDto.Path); } else { - currentSheetIndex = existSheetDto.SheetIdx; + _currentSheetIndex = existSheetDto.SheetIdx; _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); - CreateSheetXml(_value, existSheetDto.Path); + 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) @@ -120,15 +121,17 @@ public void 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(); _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; @@ -252,27 +258,30 @@ private void 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 != default) + if (_configuration.FastMode && dimensionPlaceholderPostition != 0) { WriteDimension(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); } if (_configuration.EnableAutoWidth) { - OverWriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths.Columns); + 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) @@ -339,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) { @@ -399,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; } } @@ -482,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/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/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/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; } } 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..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) } } }; @@ -54,10 +55,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,35 +108,44 @@ 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)); } - 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 = @"col1 -世界你好 -"; + var expected = + """ + col1 + 世界你好 + + """; Assert.Equal(expected, File.ReadAllText(path)); } } @@ -138,15 +156,21 @@ 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"); - 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)); } } @@ -159,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()); } } @@ -186,7 +202,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 +231,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 +262,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 +298,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 +309,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,16 +346,17 @@ 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" } })); ; + 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); - 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]); @@ -356,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"]); } @@ -399,7 +434,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]); @@ -434,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); } } } @@ -450,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); } } } @@ -466,10 +504,13 @@ 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(); - 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); @@ -485,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]; @@ -600,16 +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(); - 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); @@ -643,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); } } @@ -691,11 +740,13 @@ 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"); - MiniExcel.SaveAs(path, reader); + var rowsWritten = await MiniExcel.SaveAsAsync(path, reader); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); var q = await MiniExcel.QueryAsync(path, true); var rows = q.ToList(); @@ -712,7 +763,9 @@ public async Task EmptyDataReaderIssue() var path = PathHelper.GetTempPath(); var reader = Substitute.For(); - MiniExcel.SaveAs(path, reader, overwriteFile: true); + var rowsWritten = await MiniExcel.SaveAsAsync(path, reader, overwriteFile: true); + Assert.Single(rowsWritten); + Assert.Equal(0, rowsWritten[0]); var q = await MiniExcel.QueryAsync(path, true); var rows = q.ToList(); @@ -727,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); @@ -741,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"]); @@ -814,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"]); @@ -833,23 +894,29 @@ 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); - 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"); - MiniExcel.SaveAs(outputPath, rows); + var rowsWritten = await MiniExcel.SaveAsAsync(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 +934,11 @@ public async Task Issue89() Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); var outputPath = PathHelper.GetTempPath(); - MiniExcel.SaveAs(outputPath, rows); + var rowsWritten = await MiniExcel.SaveAsAsync(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); @@ -895,24 +966,25 @@ 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(); Assert.Equal("Name", rows[0].B); Assert.Equal("Limit", rows[0].C); - File.Delete(path); } @@ -926,7 +998,6 @@ public async Task Issue217() Assert.Equal("Name", rows[0].B); Assert.Equal("Limit", rows[0].C); - File.Delete(path); } } @@ -950,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] @@ -958,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 { @@ -1047,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(); @@ -1265,9 +1336,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)); { @@ -1288,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)); } } @@ -1406,12 +1483,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 +1501,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 +1530,6 @@ public async Task Issue157() Assert.Equal(1, rows[0].IgnoredProperty); } } - } /// @@ -1492,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]); @@ -1508,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]); @@ -1531,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); } /// @@ -1547,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"]); @@ -1562,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"]); @@ -1573,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"]); } } @@ -1582,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"]); @@ -1595,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 7cb626f8..76a7a1a2 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; } /// @@ -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); @@ -295,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); @@ -321,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 } }; @@ -787,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); @@ -803,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(); @@ -997,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"), }; @@ -1010,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(); }); } @@ -1103,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} }; @@ -1116,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} }; @@ -1142,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(); @@ -1155,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(); @@ -1323,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)); } /// @@ -1513,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); } /// @@ -1859,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)); } @@ -1885,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)); } } @@ -1903,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)); } } @@ -1934,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)}, }; @@ -2054,7 +2091,7 @@ public void Issue132() MiniExcel.SaveAs(path, value); } } - + /// /// Support SaveAs by DataSet #235 /// @@ -2067,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]); @@ -2185,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); } } } @@ -2201,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); } } } @@ -2384,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); } } @@ -2522,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); @@ -2671,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] @@ -2679,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 { @@ -2954,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(); @@ -2979,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); @@ -2995,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); @@ -3019,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)); } } @@ -3210,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]); @@ -3222,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]); @@ -3246,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); } /// @@ -3256,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"]); @@ -3276,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"]); @@ -3287,7 +3355,7 @@ public void Issue137() } { var row = rows[2] as IDictionary; - Assert.Equal(0.95, row["A"]); + Assert.Equal(0.95, row!["A"]); } } @@ -3295,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"]); @@ -3308,7 +3376,7 @@ public void Issue137() { var row = rows[1] as IDictionary; - Assert.Equal(0.95, row["比例"]); + Assert.Equal(0.95, row!["比例"]); } } @@ -3721,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( @@ -3760,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; @@ -3842,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 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];