Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added samples/xlsx/TestIssue732_1.xlsx
Binary file not shown.
Binary file added samples/xlsx/TestIssue732_2.xlsx
Binary file not shown.
Binary file added samples/xlsx/TestIssue732_3.xlsx
Binary file not shown.
109 changes: 69 additions & 40 deletions src/MiniExcel/Csv/CsvWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -53,29 +55,28 @@ 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);
}

private string GetHeader(List<ExcelColumnInfo> 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)
Expand All @@ -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<int> 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))
Expand All @@ -118,65 +126,85 @@ 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))
{
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
else
{
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<int[]> SaveAsAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

var seperator = _configuration.Seperator.ToString();
var newLine = _configuration.NewLine;

if (_value == null)
{
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<int> 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)
Expand All @@ -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;
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/MiniExcel/IExcelWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int[]> SaveAsAsync(CancellationToken cancellationToken = default);
int Insert(bool overwriteSheet = false);
Task<int> InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default);
}
}
56 changes: 23 additions & 33 deletions src/MiniExcel/MiniExcel.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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)
{
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 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)
{
stream.Seek(0, SeekOrigin.End);
// reuse code
if (excelType == ExcelType.CSV)
{
object v = null;
{
if (!(value is IEnumerable) && !(value is IDataReader) && !(value is IDictionary<string, object>) && !(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<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))
{
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<int[]> 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))
Expand Down Expand Up @@ -108,7 +98,7 @@ public static async Task InsertAsync(this Stream stream, object value, string sh

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))
{
TaskCompletionSource<IEnumerable<dynamic>> tcs = new TaskCompletionSource<IEnumerable<dynamic>>();
var tcs = new TaskCompletionSource<IEnumerable<dynamic>>();
cancellationToken.Register(() =>
{
tcs.TrySetCanceled();
Expand Down
Loading