Skip to content

Commit 578d98c

Browse files
authored
feat: support insert sheet (#709)
* feat: support insert sheet * optimize code * fix: async method bug
1 parent 972663f commit 578d98c

16 files changed

+2782
-318
lines changed

src/MiniExcel/Csv/CsvWriter.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ public void SaveAs()
6464
}
6565
}
6666

67+
public async Task SaveAsAsync(CancellationToken cancellationToken = default)
68+
{
69+
await Task.Run(() => SaveAs(), cancellationToken).ConfigureAwait(false);
70+
}
71+
72+
public void Insert(bool overwriteSheet = false)
73+
{
74+
SaveAs();
75+
}
76+
77+
public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default)
78+
{
79+
await Task.Run(() => SaveAs(), cancellationToken).ConfigureAwait(false);
80+
}
81+
6782
private void GenerateSheetByIEnumerable(IEnumerable values, string seperator, string newLine, StreamWriter writer)
6883
{
6984
Type genericType = null;
@@ -130,16 +145,6 @@ private void GenerateSheetByIEnumerable(IEnumerable values, string seperator, st
130145
}
131146
}
132147

133-
public void Insert()
134-
{
135-
SaveAs();
136-
}
137-
138-
public async Task SaveAsAsync(CancellationToken cancellationToken = default(CancellationToken))
139-
{
140-
await Task.Run(() => SaveAs(), cancellationToken).ConfigureAwait(false);
141-
}
142-
143148
private void GenerateSheetByIDataReader(IDataReader reader, string seperator, string newLine, StreamWriter writer)
144149
{
145150
int fieldCount = reader.FieldCount;

src/MiniExcel/IExcelWriter.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ namespace MiniExcelLibs
66
internal interface IExcelWriter
77
{
88
void SaveAs();
9-
Task SaveAsAsync(CancellationToken cancellationToken = default(CancellationToken));
10-
void Insert();
9+
Task SaveAsAsync(CancellationToken cancellationToken = default);
10+
void Insert(bool overwriteSheet = false);
11+
Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default);
1112
}
1213
}

src/MiniExcel/MiniExcel.Async.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,67 @@
11
namespace MiniExcelLibs
22
{
3+
using MiniExcelLibs.OpenXml;
34
using System;
5+
using System.Collections;
46
using System.Collections.Generic;
57
using System.Data;
68
using System.IO;
9+
using System.Linq;
710
using System.Threading;
811
using System.Threading.Tasks;
912
using Utils;
1013

1114
public static partial class MiniExcel
1215
{
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)
17+
{
18+
if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm")
19+
throw new NotSupportedException("MiniExcel Insert not support xlsm");
20+
21+
if (!File.Exists(path))
22+
{
23+
await SaveAsAsync(path, value, printHeader, sheetName, excelType, cancellationToken: cancellationToken);
24+
}
25+
else
26+
{
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+
}
37+
}
38+
}
39+
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)
41+
{
42+
stream.Seek(0, SeekOrigin.End);
43+
// reuse code
44+
if (excelType == ExcelType.CSV)
45+
{
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);
54+
}
55+
else
56+
{
57+
if (configuration == null)
58+
{
59+
configuration = new OpenXmlConfiguration { FastMode = true };
60+
}
61+
await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).InsertAsync(overwriteSheet);
62+
}
63+
}
64+
1365
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))
1466
{
1567
if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm")

src/MiniExcel/MiniExcel.cs

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,53 @@ public static MiniExcelDataReader GetReader(this Stream stream, bool useHeaderRo
2424
return new MiniExcelDataReader(stream, useHeaderRow, sheetName, excelType, startCell, configuration);
2525
}
2626

27-
public static void Insert(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
27+
public static void Insert(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false)
2828
{
29-
if (Path.GetExtension(path).ToLowerInvariant() != ".csv")
30-
throw new NotSupportedException("MiniExcel only support csv insert now");
29+
if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm")
30+
throw new NotSupportedException("MiniExcel Insert not support xlsm");
3131

32-
using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan))
33-
Insert(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration);
32+
if (!File.Exists(path))
33+
{
34+
SaveAs(path, value, printHeader, sheetName, excelType);
35+
}
36+
else
37+
{
38+
if (excelType == ExcelType.CSV)
39+
{
40+
using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan))
41+
Insert(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet);
42+
}
43+
else
44+
{
45+
using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan))
46+
Insert(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet);
47+
}
48+
}
3449
}
3550

36-
public static void Insert(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null)
51+
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)
3752
{
38-
if (excelType != ExcelType.CSV)
39-
throw new NotSupportedException("MiniExcel only support csv insert now");
40-
53+
stream.Seek(0, SeekOrigin.End);
4154
// reuse code
42-
object v = null;
55+
if (excelType == ExcelType.CSV)
4356
{
44-
if (!(value is IEnumerable) && !(value is IDataReader) && !(value is IDictionary<string, object>) && !(value is IDictionary))
45-
v = Enumerable.Range(0, 1).Select(s => value);
46-
else
47-
v = value;
57+
object v = null;
58+
{
59+
if (!(value is IEnumerable) && !(value is IDataReader) && !(value is IDictionary<string, object>) && !(value is IDictionary))
60+
v = Enumerable.Range(0, 1).Select(s => value);
61+
else
62+
v = value;
63+
}
64+
ExcelWriterFactory.GetProvider(stream, v, sheetName, excelType, configuration, false).Insert(overwriteSheet);
65+
}
66+
else
67+
{
68+
if (configuration == null)
69+
{
70+
configuration = new OpenXmlConfiguration { FastMode = true };
71+
}
72+
ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).Insert(overwriteSheet);
4873
}
49-
50-
stream.Seek(0, SeekOrigin.End);
51-
ExcelWriterFactory.GetProvider(stream, v, sheetName, excelType, configuration, false).Insert();
5274
}
5375

5476
public static void SaveAs(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false)

src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs

Lines changed: 106 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using MiniExcelLibs.OpenXml.Constants;
2+
using MiniExcelLibs.OpenXml.Models;
3+
using MiniExcelLibs.OpenXml.Styles;
24
using MiniExcelLibs.Utils;
35
using MiniExcelLibs.Zip;
46
using System;
@@ -15,7 +17,7 @@ namespace MiniExcelLibs.OpenXml
1517
{
1618
internal partial class ExcelOpenXmlSheetWriter : IExcelWriter
1719
{
18-
public async Task SaveAsAsync(CancellationToken cancellationToken = default(CancellationToken))
20+
public async Task SaveAsAsync(CancellationToken cancellationToken = default)
1921
{
2022
await GenerateDefaultOpenXmlAsync(cancellationToken);
2123

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

37+
public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default)
38+
{
39+
if (!_configuration.FastMode)
40+
{
41+
throw new InvalidOperationException("Insert requires fast mode to be enabled");
42+
}
43+
44+
var sheetRecords = new ExcelOpenXmlSheetReader(_stream, _configuration).GetWorkbookRels(_archive.Entries).ToArray();
45+
foreach (var sheetRecord in sheetRecords.OrderBy(o => o.Id))
46+
{
47+
_sheets.Add(new SheetDto { Name = sheetRecord.Name, SheetIdx = (int)sheetRecord.Id, State = sheetRecord.State });
48+
}
49+
var existSheetDto = _sheets.SingleOrDefault(s => s.Name == _defaultSheetName);
50+
if (existSheetDto != null && !overwriteSheet)
51+
{
52+
throw new Exception($"Sheet “{_defaultSheetName}” already exist");
53+
}
54+
55+
await GenerateStylesXmlAsync(cancellationToken);//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改
56+
57+
if (existSheetDto == null)
58+
{
59+
currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1;
60+
var insertSheetInfo = GetSheetInfos(_defaultSheetName);
61+
var insertSheetDto = insertSheetInfo.ToDto(currentSheetIndex);
62+
_sheets.Add(insertSheetDto);
63+
await CreateSheetXmlAsync(_value, insertSheetDto.Path, cancellationToken);
64+
}
65+
else
66+
{
67+
currentSheetIndex = existSheetDto.SheetIdx;
68+
_archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete();
69+
_archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(currentSheetIndex))?.Delete();
70+
_archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(currentSheetIndex))?.Delete();
71+
await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken);
72+
}
73+
74+
await AddFilesToZipAsync(cancellationToken);
75+
76+
await GenerateDrawinRelXmlAsync(currentSheetIndex, cancellationToken);
77+
78+
await GenerateDrawingXmlAsync(currentSheetIndex, cancellationToken);
79+
80+
GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary<int, string> sheetsRelsXml);
81+
82+
foreach (var sheetRelsXml in sheetsRelsXml)
83+
{
84+
var sheetRelsXmlPath = ExcelFileNames.SheetRels(sheetRelsXml.Key);
85+
_archive.Entries.SingleOrDefault(s => s.FullName == sheetRelsXmlPath)?.Delete();
86+
await CreateZipEntryAsync(sheetRelsXmlPath, null, ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), cancellationToken);
87+
}
88+
89+
_archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete();
90+
await CreateZipEntryAsync(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), cancellationToken);
91+
92+
_archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete();
93+
await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken);
94+
95+
_archive.Dispose();
96+
}
97+
3598
internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken)
3699
{
37100
await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken);
@@ -589,7 +652,7 @@ private async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IE
589652
await writer.WriteAsync(WorksheetXml.EndCols);
590653
}
591654

592-
private static async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List<ExcelColumnInfo> props)
655+
private async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List<ExcelColumnInfo> props)
593656
{
594657
var xIndex = 1;
595658
var yIndex = 1;
@@ -658,9 +721,9 @@ private async Task<int> GenerateSheetByColumnInfoAsync<T>(MiniExcelAsyncStreamWr
658721
return yIndex - 1;
659722
}
660723

661-
private static async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, string cellReference, string columnName)
724+
private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, string cellReference, string columnName)
662725
{
663-
await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", "1", ExcelOpenXmlUtils.EncodeXML(columnName)));
726+
await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", GetCellXfId("1"), ExcelOpenXmlUtils.EncodeXML(columnName)));
664727
}
665728

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

671734
if (_configuration.EnableWriteNullValueCell && valueIsNull)
672735
{
673-
await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, "2"));
736+
await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2")));
674737
return;
675738
}
676739

@@ -697,7 +760,7 @@ private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowInde
697760
}
698761
}
699762

700-
await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, styleIndex, cellValue, preserveSpace: preserveSpace, columnType: columnType));
763+
await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, GetCellXfId(styleIndex), cellValue, preserveSpace: preserveSpace, columnType: columnType));
701764
widthCollection?.AdjustWidth(cellIndex, cellValue);
702765
}
703766

@@ -727,41 +790,59 @@ private async Task AddFilesToZipAsync(CancellationToken cancellationToken)
727790
/// </summary>
728791
private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken)
729792
{
730-
var styleXml = GetStylesXml(_configuration.DynamicColumns);
731-
732-
await CreateZipEntryAsync(
733-
ExcelFileNames.Styles,
734-
ExcelContentTypes.Styles,
735-
styleXml,
736-
cancellationToken);
793+
using (var context = new SheetStyleBuildContext(_zipDictionary, _archive, _utf8WithBom, _configuration.DynamicColumns))
794+
{
795+
var builder = (ISheetStyleBuilder)null;
796+
switch (_configuration.TableStyles)
797+
{
798+
case TableStyles.None:
799+
builder = new MinimalSheetStyleBuilder(context);
800+
break;
801+
case TableStyles.Default:
802+
builder = new DefaultSheetStyleBuilder(context);
803+
break;
804+
}
805+
var result = await builder.BuildAsync(cancellationToken);
806+
cellXfIdMap = result.CellXfIdMap;
807+
}
737808
}
738809

739810
private async Task GenerateDrawinRelXmlAsync(CancellationToken cancellationToken)
740811
{
741812
for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++)
742813
{
743-
var drawing = GetDrawingRelationshipXml(sheetIndex);
744-
await CreateZipEntryAsync(
745-
ExcelFileNames.DrawingRels(sheetIndex),
746-
string.Empty,
747-
ExcelXml.DefaultDrawingXmlRels.Replace("{{format}}", drawing),
748-
cancellationToken);
814+
await GenerateDrawinRelXmlAsync(sheetIndex, cancellationToken);
749815
}
750816
}
751817

818+
private async Task GenerateDrawinRelXmlAsync(int sheetIndex, CancellationToken cancellationToken)
819+
{
820+
var drawing = GetDrawingRelationshipXml(sheetIndex);
821+
await CreateZipEntryAsync(
822+
ExcelFileNames.DrawingRels(sheetIndex),
823+
string.Empty,
824+
ExcelXml.DefaultDrawingXmlRels.Replace("{{format}}", drawing),
825+
cancellationToken);
826+
}
827+
752828
private async Task GenerateDrawingXmlAsync(CancellationToken cancellationToken)
753829
{
754830
for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++)
755831
{
756-
var drawing = GetDrawingXml(sheetIndex);
757-
await CreateZipEntryAsync(
758-
ExcelFileNames.Drawing(sheetIndex),
759-
ExcelContentTypes.Drawing,
760-
ExcelXml.DefaultDrawing.Replace("{{format}}", drawing),
761-
cancellationToken);
832+
await GenerateDrawingXmlAsync(sheetIndex, cancellationToken);
762833
}
763834
}
764835

836+
private async Task GenerateDrawingXmlAsync(int sheetIndex, CancellationToken cancellationToken)
837+
{
838+
var drawing = GetDrawingXml(sheetIndex);
839+
await CreateZipEntryAsync(
840+
ExcelFileNames.Drawing(sheetIndex),
841+
ExcelContentTypes.Drawing,
842+
ExcelXml.DefaultDrawing.Replace("{{format}}", drawing),
843+
cancellationToken);
844+
}
845+
765846
/// <summary>
766847
/// workbook.xml 、 workbookRelsXml
767848
/// </summary>

0 commit comments

Comments
 (0)