Skip to content

Commit 6105d3d

Browse files
Fixed cancellation token not working for some async methods, enhanced export methods by returning number of rows, added active tab functionality, tests and code cleanup (#738)
* Enhanced methods Insert, SaveAs and relative async versione - The methods Insert / InsertAsync now return the number of rows inserted into the worksheet, while SaveAs / SaveAsAsync return an int array representing the rows inserted into each worksheet - Added asserts in tests to that check for aferomentioned return values to be correct, plus a little bit of tiding said tests - Minor code cleanup (denesting branches, renaming variables, turning methods that don't change state of the object into static functions...) * Added cancellation token to async methods missing it - Fixed methods InsertAsync and SaveAsAsync not forwarding the cancellation token to the callees, preventing the actual cancellation of the processes from the outside - Added cancellation token support to a number of private and internal async methods, and increased the frequency of cancellation tokens checking whether they've been cancelled, in order to make the reach termination of the process as close as to when it's requested as possible * Addendum Some more renaming / cleaning up, especially in the tests * Added active tab functionality Adde property Active in classes SheetInfo and SheetRecord to indicate wheter the worksheet referenced represents the active tab
1 parent e445110 commit 6105d3d

23 files changed

+1271
-911
lines changed

samples/xlsx/TestIssue732_1.xlsx

7.08 KB
Binary file not shown.

samples/xlsx/TestIssue732_2.xlsx

7.08 KB
Binary file not shown.

samples/xlsx/TestIssue732_3.xlsx

5.99 KB
Binary file not shown.

src/MiniExcel/Csv/CsvWriter.cs

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,35 @@ internal class CsvWriter : IExcelWriter, IDisposable
1818
private readonly bool _printHeader;
1919
private object _value;
2020
private readonly StreamWriter _writer;
21-
private bool disposedValue;
21+
private bool _disposedValue;
2222

2323
public CsvWriter(Stream stream, object value, IConfiguration configuration, bool printHeader)
2424
{
25-
this._stream = stream;
26-
this._configuration = configuration == null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration;
27-
this._printHeader = printHeader;
28-
this._value = value;
29-
this._writer = _configuration.StreamWriterFunc(_stream);
25+
_stream = stream;
26+
_configuration = configuration == null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration;
27+
_printHeader = printHeader;
28+
_value = value;
29+
_writer = _configuration.StreamWriterFunc(_stream);
3030
}
3131

32-
public void SaveAs()
32+
public int[] SaveAs()
3333
{
3434
if (_value == null)
3535
{
3636
_writer.Write("");
3737
_writer.Flush();
38-
return;
38+
return new int[0];
3939
}
4040

41-
WriteValues(_writer, _value);
41+
var rowsWritten = WriteValues(_value);
4242
_writer.Flush();
43+
44+
return new[] { rowsWritten };
4345
}
4446

45-
public void Insert(bool overwriteSheet = false)
47+
public int Insert(bool overwriteSheet = false)
4648
{
47-
SaveAs();
49+
return SaveAs().FirstOrDefault();
4850
}
4951

5052
private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column)
@@ -53,29 +55,28 @@ private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column)
5355
rowBuilder.Append(_configuration.Seperator);
5456
}
5557

56-
private void RemoveTrailingSeparator(StringBuilder rowBuilder)
58+
private static void RemoveTrailingSeparator(StringBuilder rowBuilder)
5759
{
5860
if (rowBuilder.Length == 0)
59-
{
6061
return;
61-
}
62+
6263
rowBuilder.Remove(rowBuilder.Length - 1, 1);
6364
}
6465

6566
private string GetHeader(List<ExcelColumnInfo> props) => string.Join(
6667
_configuration.Seperator.ToString(),
6768
props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration.AlwaysQuote, _configuration.Seperator)));
6869

69-
private void WriteValues(StreamWriter writer, object values)
70+
private int WriteValues(object values)
7071
{
71-
IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration);
72+
var writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration);
7273

7374
var props = writeAdapter.GetColumns();
7475
if (props == null)
7576
{
7677
_writer.Write(_configuration.NewLine);
7778
_writer.Flush();
78-
return;
79+
return 0;
7980
}
8081

8182
if (_printHeader)
@@ -84,25 +85,32 @@ private void WriteValues(StreamWriter writer, object values)
8485
_writer.Write(_configuration.NewLine);
8586
}
8687

88+
if (writeAdapter == null)
89+
return 0;
90+
8791
var rowBuilder = new StringBuilder();
88-
if (writeAdapter != null)
92+
var rowsWritten = 0;
93+
94+
foreach (var row in writeAdapter.GetRows(props))
8995
{
90-
foreach (var row in writeAdapter.GetRows(props))
96+
rowBuilder.Clear();
97+
foreach (var column in row)
9198
{
92-
rowBuilder.Clear();
93-
foreach (var column in row)
94-
{
95-
AppendColumn(rowBuilder, column);
96-
}
97-
RemoveTrailingSeparator(rowBuilder);
98-
_writer.Write(rowBuilder.ToString());
99-
_writer.Write(_configuration.NewLine);
99+
AppendColumn(rowBuilder, column);
100100
}
101+
RemoveTrailingSeparator(rowBuilder);
102+
_writer.Write(rowBuilder.ToString());
103+
_writer.Write(_configuration.NewLine);
104+
105+
rowsWritten++;
101106
}
107+
return rowsWritten;
102108
}
103109

104-
private async Task WriteValuesAsync(StreamWriter writer, object values, string seperator, string newLine, CancellationToken cancellationToken)
110+
private async Task<int> WriteValuesAsync(StreamWriter writer, object values, string seperator, string newLine, CancellationToken cancellationToken)
105111
{
112+
cancellationToken.ThrowIfCancellationRequested();
113+
106114
#if NETSTANDARD2_0_OR_GREATER || NET
107115
IMiniExcelWriteAdapter writeAdapter = null;
108116
if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter))
@@ -118,65 +126,85 @@ private async Task WriteValuesAsync(StreamWriter writer, object values, string s
118126
{
119127
await _writer.WriteAsync(_configuration.NewLine);
120128
await _writer.FlushAsync();
121-
return;
129+
return 0;
122130
}
131+
123132
if (_printHeader)
124133
{
125134
await _writer.WriteAsync(GetHeader(props));
126135
await _writer.WriteAsync(newLine);
127136
}
137+
128138
var rowBuilder = new StringBuilder();
139+
var rowsWritten = 0;
140+
129141
if (writeAdapter != null)
130142
{
131143
foreach (var row in writeAdapter.GetRows(props, cancellationToken))
132144
{
133145
rowBuilder.Clear();
134146
foreach (var column in row)
135147
{
148+
cancellationToken.ThrowIfCancellationRequested();
136149
AppendColumn(rowBuilder, column);
137150
}
151+
138152
RemoveTrailingSeparator(rowBuilder);
139153
await _writer.WriteAsync(rowBuilder.ToString());
140154
await _writer.WriteAsync(newLine);
155+
156+
rowsWritten++;
141157
}
142158
}
143159
#if NETSTANDARD2_0_OR_GREATER || NET
144160
else
145161
{
146162
await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken))
147163
{
164+
cancellationToken.ThrowIfCancellationRequested();
148165
rowBuilder.Clear();
166+
149167
await foreach (var column in row)
150168
{
169+
cancellationToken.ThrowIfCancellationRequested();
151170
AppendColumn(rowBuilder, column);
152171
}
172+
153173
RemoveTrailingSeparator(rowBuilder);
154174
await _writer.WriteAsync(rowBuilder.ToString());
155175
await _writer.WriteAsync(newLine);
176+
177+
rowsWritten++;
156178
}
157179
}
158180
#endif
181+
return rowsWritten;
159182
}
160183

161-
public async Task SaveAsAsync(CancellationToken cancellationToken = default)
184+
public async Task<int[]> SaveAsAsync(CancellationToken cancellationToken = default)
162185
{
186+
cancellationToken.ThrowIfCancellationRequested();
187+
163188
var seperator = _configuration.Seperator.ToString();
164189
var newLine = _configuration.NewLine;
165190

166191
if (_value == null)
167192
{
168193
await _writer.WriteAsync("");
169194
await _writer.FlushAsync();
170-
return;
195+
return new int[0];
171196
}
172197

173-
await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken);
198+
var rowsWritten = await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken);
174199
await _writer.FlushAsync();
200+
201+
return new[] { rowsWritten };
175202
}
176203

177-
public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default)
204+
public async Task<int> InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default)
178205
{
179-
await SaveAsAsync(cancellationToken);
206+
var rowsWritten = await SaveAsAsync(cancellationToken);
207+
return rowsWritten.FirstOrDefault();
180208
}
181209

182210
public string ToCsvString(object value, ExcelColumnInfo p)
@@ -190,29 +218,30 @@ public string ToCsvString(object value, ExcelColumnInfo p)
190218
{
191219
return dateTime.ToString(p.ExcelFormat, _configuration.Culture);
192220
}
193-
return _configuration.Culture.Equals(CultureInfo.InvariantCulture) ? dateTime.ToString("yyyy-MM-dd HH:mm:ss", _configuration.Culture) : dateTime.ToString(_configuration.Culture);
221+
return _configuration.Culture.Equals(CultureInfo.InvariantCulture)
222+
? dateTime.ToString("yyyy-MM-dd HH:mm:ss", _configuration.Culture)
223+
: dateTime.ToString(_configuration.Culture);
194224
}
225+
195226
if (p?.ExcelFormat != null && value is IFormattable formattableValue)
196-
{
197227
return formattableValue.ToString(p.ExcelFormat, _configuration.Culture);
198-
}
199228

200229
return Convert.ToString(value, _configuration.Culture);
201230
}
202231

203232
protected virtual void Dispose(bool disposing)
204233
{
205-
if (!disposedValue)
234+
if (!_disposedValue)
206235
{
207236
if (disposing)
208237
{
209-
this._writer.Dispose();
238+
_writer.Dispose();
210239
// TODO: dispose managed state (managed objects)
211240
}
212241

213242
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
214243
// TODO: set large fields to null
215-
disposedValue = true;
244+
_disposedValue = true;
216245
}
217246
}
218247

src/MiniExcel/IExcelWriter.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ namespace MiniExcelLibs
55
{
66
internal interface IExcelWriter
77
{
8-
void SaveAs();
9-
Task SaveAsAsync(CancellationToken cancellationToken = default);
10-
void Insert(bool overwriteSheet = false);
11-
Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default);
8+
int[] SaveAs();
9+
Task<int[]> SaveAsAsync(CancellationToken cancellationToken = default);
10+
int Insert(bool overwriteSheet = false);
11+
Task<int> InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default);
1212
}
1313
}

src/MiniExcel/MiniExcel.Async.cs

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,67 +13,57 @@
1313

1414
public static partial class MiniExcel
1515
{
16-
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)
16+
public static async Task<int> InsertAsync(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default)
1717
{
1818
if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm")
19-
throw new NotSupportedException("MiniExcel Insert not support xlsm");
19+
throw new NotSupportedException("MiniExcel's Insert does not support the .xlsm format");
2020

2121
if (!File.Exists(path))
2222
{
23-
await SaveAsAsync(path, value, printHeader, sheetName, excelType, cancellationToken: cancellationToken);
23+
var rowsWritten = await SaveAsAsync(path, value, printHeader, sheetName, excelType, configuration, cancellationToken: cancellationToken);
24+
return rowsWritten.FirstOrDefault();
25+
}
26+
27+
if (excelType == ExcelType.CSV)
28+
{
29+
using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan))
30+
return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken);
2431
}
2532
else
2633
{
27-
if (excelType == ExcelType.CSV)
28-
{
29-
using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan))
30-
await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken);
31-
}
32-
else
33-
{
34-
using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan))
35-
await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken);
36-
}
34+
using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan))
35+
return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken);
3736
}
3837
}
3938

40-
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)
39+
public static async Task<int>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)
4140
{
4241
stream.Seek(0, SeekOrigin.End);
4342
// reuse code
4443
if (excelType == ExcelType.CSV)
4544
{
46-
object v = null;
47-
{
48-
if (!(value is IEnumerable) && !(value is IDataReader) && !(value is IDictionary<string, object>) && !(value is IDictionary))
49-
v = Enumerable.Range(0, 1).Select(s => value);
50-
else
51-
v = value;
52-
}
53-
await ExcelWriterFactory.GetProvider(stream, v, sheetName, excelType, configuration, false).InsertAsync(overwriteSheet);
45+
var newValue = value is IEnumerable || value is IDataReader ? value : new[]{value}.AsEnumerable();
46+
return await ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false).InsertAsync(overwriteSheet, cancellationToken);
5447
}
5548
else
5649
{
57-
if (configuration == null)
58-
{
59-
configuration = new OpenXmlConfiguration { FastMode = true };
60-
}
61-
await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).InsertAsync(overwriteSheet);
50+
var configOrDefault = configuration ?? new OpenXmlConfiguration { FastMode = true };
51+
return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configOrDefault, printHeader).InsertAsync(overwriteSheet, cancellationToken);
6252
}
6353
}
6454

65-
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))
55+
public static async Task<int[]> 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))
6656
{
6757
if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm")
68-
throw new NotSupportedException("MiniExcel SaveAs not support xlsm");
58+
throw new NotSupportedException("MiniExcel's SaveAs does not support the .xlsm format");
6959

7060
using (var stream = overwriteFile ? File.Create(path) : new FileStream(path, FileMode.CreateNew))
71-
await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration);
61+
return await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, cancellationToken);
7262
}
7363

74-
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))
64+
public static async Task<int[]> SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken))
7565
{
76-
await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAsAsync(cancellationToken);
66+
return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAsAsync(cancellationToken);
7767
}
7868

7969
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
10898

10999
public static async Task<IEnumerable<dynamic>> QueryAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken))
110100
{
111-
TaskCompletionSource<IEnumerable<dynamic>> tcs = new TaskCompletionSource<IEnumerable<dynamic>>();
101+
var tcs = new TaskCompletionSource<IEnumerable<dynamic>>();
112102
cancellationToken.Register(() =>
113103
{
114104
tcs.TrySetCanceled();

0 commit comments

Comments
 (0)