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
25 changes: 15 additions & 10 deletions src/MiniExcel/Csv/CsvWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ public void SaveAs()
}
}

public async Task SaveAsAsync(CancellationToken cancellationToken = default)
{
await Task.Run(() => SaveAs(), cancellationToken).ConfigureAwait(false);
}

public void Insert(bool overwriteSheet = false)
{
SaveAs();
}

public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default)
{
await Task.Run(() => SaveAs(), cancellationToken).ConfigureAwait(false);
}

private void GenerateSheetByIEnumerable(IEnumerable values, string seperator, string newLine, StreamWriter writer)
{
Type genericType = null;
Expand Down Expand Up @@ -130,16 +145,6 @@ private void GenerateSheetByIEnumerable(IEnumerable values, string seperator, st
}
}

public void Insert()
{
SaveAs();
}

public async Task SaveAsAsync(CancellationToken cancellationToken = default(CancellationToken))
{
await Task.Run(() => SaveAs(), cancellationToken).ConfigureAwait(false);
}

private void GenerateSheetByIDataReader(IDataReader reader, string seperator, string newLine, StreamWriter writer)
{
int fieldCount = reader.FieldCount;
Expand Down
5 changes: 3 additions & 2 deletions src/MiniExcel/IExcelWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace MiniExcelLibs
internal interface IExcelWriter
{
void SaveAs();
Task SaveAsAsync(CancellationToken cancellationToken = default(CancellationToken));
void Insert();
Task SaveAsAsync(CancellationToken cancellationToken = default);
void Insert(bool overwriteSheet = false);
Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default);
}
}
52 changes: 52 additions & 0 deletions src/MiniExcel/MiniExcel.Async.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,67 @@
namespace MiniExcelLibs
{
using MiniExcelLibs.OpenXml;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Utils;

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)
{
if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm")
throw new NotSupportedException("MiniExcel Insert not support xlsm");

if (!File.Exists(path))
{
await SaveAsAsync(path, value, printHeader, sheetName, excelType, cancellationToken: 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);
}
}
}

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)
{
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);
}
else
{
if (configuration == null)
{
configuration = new OpenXmlConfiguration { FastMode = true };
}
await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).InsertAsync(overwriteSheet);
}
}

public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default(CancellationToken))
{
if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm")
Expand Down
56 changes: 39 additions & 17 deletions src/MiniExcel/MiniExcel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,53 @@ 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)
public static void 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() != ".csv")
throw new NotSupportedException("MiniExcel only support csv insert now");
if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm")
throw new NotSupportedException("MiniExcel Insert not support xlsm");

using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan))
Insert(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration);
if (!File.Exists(path))
{
SaveAs(path, value, printHeader, sheetName, excelType);
}
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);
}
}
}

public static void Insert(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null)
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)
{
if (excelType != ExcelType.CSV)
throw new NotSupportedException("MiniExcel only support csv insert now");

stream.Seek(0, SeekOrigin.End);
// reuse code
object v = null;
if (excelType == ExcelType.CSV)
{
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;
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;
}
ExcelWriterFactory.GetProvider(stream, v, 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);
}

stream.Seek(0, SeekOrigin.End);
ExcelWriterFactory.GetProvider(stream, v, sheetName, excelType, configuration, false).Insert();
}

public static void SaveAs(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false)
Expand Down
131 changes: 106 additions & 25 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using MiniExcelLibs.OpenXml.Constants;
using MiniExcelLibs.OpenXml.Models;
using MiniExcelLibs.OpenXml.Styles;
using MiniExcelLibs.Utils;
using MiniExcelLibs.Zip;
using System;
Expand All @@ -15,7 +17,7 @@ namespace MiniExcelLibs.OpenXml
{
internal partial class ExcelOpenXmlSheetWriter : IExcelWriter
{
public async Task SaveAsAsync(CancellationToken cancellationToken = default(CancellationToken))
public async Task SaveAsAsync(CancellationToken cancellationToken = default)
{
await GenerateDefaultOpenXmlAsync(cancellationToken);

Expand All @@ -32,6 +34,67 @@ internal partial class ExcelOpenXmlSheetWriter : IExcelWriter
_archive.Dispose();
}

public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default)
{
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");
}

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();
_archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex))?.Delete();
_archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex))?.Delete();
await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken);
}

await AddFilesToZipAsync(cancellationToken);

await GenerateDrawinRelXmlAsync(currentSheetIndex, cancellationToken);

await GenerateDrawingXmlAsync(currentSheetIndex, cancellationToken);

GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary<int, string> 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.WorkbookRels)?.Delete();
await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken);

_archive.Dispose();
}

internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken)
{
await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken);
Expand Down Expand Up @@ -589,7 +652,7 @@ private async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IE
await writer.WriteAsync(WorksheetXml.EndCols);
}

private static async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List<ExcelColumnInfo> props)
private async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List<ExcelColumnInfo> props)
{
var xIndex = 1;
var yIndex = 1;
Expand Down Expand Up @@ -658,9 +721,9 @@ private async Task<int> GenerateSheetByColumnInfoAsync<T>(MiniExcelAsyncStreamWr
return yIndex - 1;
}

private static async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, string cellReference, string columnName)
private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, string cellReference, string columnName)
{
await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", "1", ExcelOpenXmlUtils.EncodeXML(columnName)));
await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", GetCellXfId("1"), ExcelOpenXmlUtils.EncodeXML(columnName)));
}

private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo p, ExcelWidthCollection widthCollection)
Expand All @@ -670,7 +733,7 @@ private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowInde

if (_configuration.EnableWriteNullValueCell && valueIsNull)
{
await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, "2"));
await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2")));
return;
}

Expand All @@ -697,7 +760,7 @@ private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowInde
}
}

await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, styleIndex, cellValue, preserveSpace: preserveSpace, columnType: columnType));
await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, GetCellXfId(styleIndex), cellValue, preserveSpace: preserveSpace, columnType: columnType));
widthCollection?.AdjustWidth(cellIndex, cellValue);
}

Expand Down Expand Up @@ -727,41 +790,59 @@ private async Task AddFilesToZipAsync(CancellationToken cancellationToken)
/// </summary>
private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken)
{
var styleXml = GetStylesXml(_configuration.DynamicColumns);

await CreateZipEntryAsync(
ExcelFileNames.Styles,
ExcelContentTypes.Styles,
styleXml,
cancellationToken);
using (var context = new SheetStyleBuildContext(_zipDictionary, _archive, _utf8WithBom, _configuration.DynamicColumns))
{
var builder = (ISheetStyleBuilder)null;
switch (_configuration.TableStyles)
{
case TableStyles.None:
builder = new MinimalSheetStyleBuilder(context);
break;
case TableStyles.Default:
builder = new DefaultSheetStyleBuilder(context);
break;
}
var result = await builder.BuildAsync(cancellationToken);
cellXfIdMap = result.CellXfIdMap;
}
}

private async Task GenerateDrawinRelXmlAsync(CancellationToken cancellationToken)
{
for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++)
{
var drawing = GetDrawingRelationshipXml(sheetIndex);
await CreateZipEntryAsync(
ExcelFileNames.DrawingRels(sheetIndex),
string.Empty,
ExcelXml.DefaultDrawingXmlRels.Replace("{{format}}", drawing),
cancellationToken);
await GenerateDrawinRelXmlAsync(sheetIndex, cancellationToken);
}
}

private async Task GenerateDrawinRelXmlAsync(int sheetIndex, CancellationToken cancellationToken)
{
var drawing = GetDrawingRelationshipXml(sheetIndex);
await CreateZipEntryAsync(
ExcelFileNames.DrawingRels(sheetIndex),
string.Empty,
ExcelXml.DefaultDrawingXmlRels.Replace("{{format}}", drawing),
cancellationToken);
}

private async Task GenerateDrawingXmlAsync(CancellationToken cancellationToken)
{
for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++)
{
var drawing = GetDrawingXml(sheetIndex);
await CreateZipEntryAsync(
ExcelFileNames.Drawing(sheetIndex),
ExcelContentTypes.Drawing,
ExcelXml.DefaultDrawing.Replace("{{format}}", drawing),
cancellationToken);
await GenerateDrawingXmlAsync(sheetIndex, cancellationToken);
}
}

private async Task GenerateDrawingXmlAsync(int sheetIndex, CancellationToken cancellationToken)
{
var drawing = GetDrawingXml(sheetIndex);
await CreateZipEntryAsync(
ExcelFileNames.Drawing(sheetIndex),
ExcelContentTypes.Drawing,
ExcelXml.DefaultDrawing.Replace("{{format}}", drawing),
cancellationToken);
}

/// <summary>
/// workbook.xml 、 workbookRelsXml
/// </summary>
Expand Down
Loading
Loading