From 8596bc4359b4167cc30ffd592214e14ab9b4af8e Mon Sep 17 00:00:00 2001 From: izanhzh Date: Tue, 17 Jun 2025 09:47:46 +0800 Subject: [PATCH 1/5] refactor code and auto-generate sync methods --- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 692 ------------------ .../OpenXml/ExcelOpenXmlSheetWriter.cs | 608 ++++++++++++++- .../OpenXml/MiniExcelAsyncStreamWriter.cs | 77 -- .../OpenXml/MiniExcelStreamWriter.cs | 49 +- 4 files changed, 618 insertions(+), 808 deletions(-) delete mode 100644 src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs delete mode 100644 src/MiniExcel/OpenXml/MiniExcelAsyncStreamWriter.cs diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs deleted file mode 100644 index 8c21ab64..00000000 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ /dev/null @@ -1,692 +0,0 @@ -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.OpenXml.Styles; -using MiniExcelLibs.Utils; -using MiniExcelLibs.WriteAdapter; -using MiniExcelLibs.Zip; -using System; -using System.Collections.Generic; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; - -namespace MiniExcelLibs.OpenXml -{ - internal partial class ExcelOpenXmlSheetWriter : IExcelWriter - { - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task SaveAsAsync(CancellationToken cancellationToken = default) - { - try - { - cancellationToken.ThrowIfCancellationRequested(); - - await GenerateDefaultOpenXmlAsync(cancellationToken).ConfigureAwait(false); - - var sheets = GetSheets(); - var rowsWritten = new List(); - - 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).ConfigureAwait(false); - rowsWritten.Add(rows); - } - - await GenerateEndXmlAsync(cancellationToken).ConfigureAwait(false); - return rowsWritten.ToArray(); - } - finally - { - _archive.Dispose(); - } - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) - { - try - { - cancellationToken.ThrowIfCancellationRequested(); - - if (!_configuration.FastMode) - throw new InvalidOperationException("Insert requires fast mode to be enabled"); - - cancellationToken.ThrowIfCancellationRequested(); - - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(_stream, _configuration, cancellationToken: cancellationToken).ConfigureAwait(false); - var sheetRecords = (await reader.GetWorkbookRelsAsync(_archive.Entries, cancellationToken).ConfigureAwait(false)).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).ConfigureAwait(false);//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 - - 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).ConfigureAwait(false); - } - else - { - _currentSheetIndex = existSheetDto.SheetIdx; - _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); - rowsWritten = await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken).ConfigureAwait(false); - } - - await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); - - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(_currentSheetIndex - 1))?.Delete(); - await GenerateDrawinRelXmlAsync(_currentSheetIndex - 1, cancellationToken).ConfigureAwait(false); - - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(_currentSheetIndex - 1))?.Delete(); - await GenerateDrawingXmlAsync(_currentSheetIndex - 1, cancellationToken).ConfigureAwait(false); - - 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).ConfigureAwait(false); - } - - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete(); - await CreateZipEntryAsync(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), cancellationToken).ConfigureAwait(false); - - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); - await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken).ConfigureAwait(false); - - await InsertContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); - - return rowsWritten; - } - finally - { - _archive.Dispose(); - } - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken) - { - await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken).ConfigureAwait(false); - await CreateZipEntryAsync(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString, cancellationToken).ConfigureAwait(false); - await GenerateStylesXmlAsync(cancellationToken).ConfigureAwait(false); - } - - private async Task CreateSheetXmlAsync(object values, string sheetPath, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); - var rowsWritten = 0; - - using (var zipStream = entry.Open()) - using (var writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken)) - { - if (values == null) - { - await WriteEmptySheetAsync(writer).ConfigureAwait(false); - } - else - { - rowsWritten = await WriteValuesAsync(writer, values, cancellationToken).ConfigureAwait(false); - } - } - _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); - return rowsWritten; - } - - private static async Task WriteEmptySheetAsync(MiniExcelAsyncStreamWriter writer) - { - await writer.WriteAsync(ExcelXml.EmptySheetXml).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteDimensionPlaceholderAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer -#else - MiniExcelAsyncStreamWriter writer -#endif - ) - { - var dimensionPlaceholderPostition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension).ConfigureAwait(false); - await writer.WriteAsync(WorksheetXml.DimensionPlaceholder).ConfigureAwait(false); // end of code will be replaced - - return dimensionPlaceholderPostition; - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteDimensionAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer, -#else - MiniExcelAsyncStreamWriter writer, -#endif - int maxRowIndex, int maxColumnIndex, long placeholderPosition) - { - // Flush and save position so that we can get back again. - var position = await writer.FlushAsync().ConfigureAwait(false); - - writer.SetPosition(placeholderPosition); - await writer.WriteAndFlushAsync($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}""").ConfigureAwait(false); - - writer.SetPosition(position); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task WriteValuesAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer, -#else - MiniExcelAsyncStreamWriter writer, -#endif - object values, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - IMiniExcelWriteAdapter writeAdapter = null; - if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) - { - writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - } - - var count = 0; - var isKnownCount = writeAdapter != null && writeAdapter.TryGetKnownCount(out count); - List props; -#if SYNC_ONLY - props = writeAdapter?.GetColumns(); -#else - props = writeAdapter != null ? writeAdapter?.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); -#endif - if (props == null) - { - await WriteEmptySheetAsync(writer).ConfigureAwait(false); - return 0; - } - int maxRowIndex; - var maxColumnIndex = props.Count(x => x != null && !x.ExcelIgnore); - - await writer.WriteAsync(WorksheetXml.StartWorksheetWithRelationship).ConfigureAwait(false); - - long dimensionPlaceholderPostition = 0; - - // We can write the dimensions directly if the row count is known - if (isKnownCount) - { - maxRowIndex = _printHeader ? count + 1 : count; - await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count))).ConfigureAwait(false); - } - else if (_configuration.FastMode) - { - dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer).ConfigureAwait(false); - } - - //sheet view - await writer.WriteAsync(GetSheetViews()).ConfigureAwait(false); - - //cols:width - ExcelWidthCollection widths = null; - long columnWidthsPlaceholderPosition = 0; - if (_configuration.EnableAutoWidth) - { - columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, maxColumnIndex, cancellationToken).ConfigureAwait(false); - widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); - } - else - { - await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken).ConfigureAwait(false); - } - - //header - await writer.WriteAsync(WorksheetXml.StartSheetData).ConfigureAwait(false); - var currentRowIndex = 0; - if (_printHeader) - { - await PrintHeaderAsync(writer, props, cancellationToken).ConfigureAwait(false); - currentRowIndex++; - } - - if (writeAdapter != null) - { - foreach (var row in writeAdapter.GetRows(props, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); - - await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)).ConfigureAwait(false); - foreach (var cellValue in row) - { - cancellationToken.ThrowIfCancellationRequested(); - await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); - } - await writer.WriteAsync(WorksheetXml.EndRow).ConfigureAwait(false); - } - } - else - { -#if !SYNC_ONLY - await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)).ConfigureAwait(false); - - await foreach (var cellValue in row.ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); - } - await writer.WriteAsync(WorksheetXml.EndRow).ConfigureAwait(false); - } -#endif - } - maxRowIndex = currentRowIndex; - - await writer.WriteAsync(WorksheetXml.EndSheetData).ConfigureAwait(false); - - if (_configuration.AutoFilter) - { - await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))).ConfigureAwait(false); - } - - await writer.WriteAsync(WorksheetXml.Drawing(_currentSheetIndex)).ConfigureAwait(false); - await writer.WriteAsync(WorksheetXml.EndWorksheet).ConfigureAwait(false); - - if (_configuration.FastMode && dimensionPlaceholderPostition != 0) - { - await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition).ConfigureAwait(false); - } - if (_configuration.EnableAutoWidth) - { - await OverwriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths?.Columns, cancellationToken).ConfigureAwait(false); - } - - if (_printHeader) - maxRowIndex--; - - return maxRowIndex; - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteColumnWidthPlaceholdersAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer, -#else - MiniExcelAsyncStreamWriter writer, -#endif - int count, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - var placeholderPosition = await writer.FlushAsync().ConfigureAwait(false); - await writer.WriteWhitespaceAsync(WorksheetXml.GetColumnPlaceholderLength(count)).ConfigureAwait(false); - return placeholderPosition; - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task OverwriteColumnWidthPlaceholdersAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer, -#else - MiniExcelAsyncStreamWriter writer, -#endif - long placeholderPosition, IEnumerable columnWidths, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - var position = await writer.FlushAsync().ConfigureAwait(false); - - writer.SetPosition(placeholderPosition); - await WriteColumnsWidthsAsync(writer, columnWidths, cancellationToken).ConfigureAwait(false); - - await writer.FlushAsync().ConfigureAwait(false); - writer.SetPosition(position); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteColumnsWidthsAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer, -#else - MiniExcelAsyncStreamWriter writer, -#endif - IEnumerable columnWidths, CancellationToken cancellationToken = default) - { - var hasWrittenStart = false; - foreach (var column in columnWidths) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (!hasWrittenStart) - { - await writer.WriteAsync(WorksheetXml.StartCols).ConfigureAwait(false); - hasWrittenStart = true; - } - await writer.WriteAsync(WorksheetXml.Column(column.Index, column.Width)).ConfigureAwait(false); - } - - if (!hasWrittenStart) - return; - - await writer.WriteAsync(WorksheetXml.EndCols).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task PrintHeaderAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer, -#else - MiniExcelAsyncStreamWriter writer, -#endif - List props, CancellationToken cancellationToken = default) - { - const int yIndex = 1; - await writer.WriteAsync(WorksheetXml.StartRow(yIndex)).ConfigureAwait(false); - - var xIndex = 1; - foreach (var p in props) - { - //reason : https://github.com/mini-software/MiniExcel/issues/142 - if (p != null) - { - if (p.ExcelIgnore) - continue; - - var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex); - await WriteCellAsync(writer, r, columnName: p.ExcelColumnName).ConfigureAwait(false); - } - xIndex++; - } - - await writer.WriteAsync(WorksheetXml.EndRow).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task WriteCellAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer, -#else - MiniExcelAsyncStreamWriter writer, -#endif - string cellReference, string columnName) - { - await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", GetCellXfId("1"), ExcelOpenXmlUtils.EncodeXML(columnName))).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task WriteCellAsync( -#if SYNC_ONLY - global::MiniExcelLibs.OpenXml.MiniExcelStreamWriter writer, -#else - MiniExcelAsyncStreamWriter writer, -#endif - int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, ExcelWidthCollection widthCollection) - { - if (columnInfo?.CustomFormatter != null) - { - try - { - value = columnInfo.CustomFormatter(value); - } - catch - { - //ignored - } - } - - var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); - var valueIsNull = value is null || - value is DBNull || - (_configuration.WriteEmptyStringAsNull && value is string vs && vs == string.Empty); - - if (_configuration.EnableWriteNullValueCell && valueIsNull) - { - await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2"))).ConfigureAwait(false); - return; - } - - var tuple = GetCellValue(rowIndex, cellIndex, value, columnInfo, valueIsNull); - - var styleIndex = tuple.Item1; - var dataType = tuple.Item2; - var cellValue = tuple.Item3; - var columnType = columnInfo.ExcelColumnType; - - /*Prefix and suffix blank space will lost after SaveAs #294*/ - var preserveSpace = cellValue != null && (cellValue.StartsWith(" ", StringComparison.Ordinal) || - cellValue.EndsWith(" ", StringComparison.Ordinal)); - - await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, GetCellXfId(styleIndex), cellValue, preserveSpace: preserveSpace, columnType: columnType)).ConfigureAwait(false); - widthCollection?.AdjustWidth(cellIndex, cellValue); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateEndXmlAsync(CancellationToken cancellationToken) - { - await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); - await GenerateDrawinRelXmlAsync(cancellationToken).ConfigureAwait(false); - await GenerateDrawingXmlAsync(cancellationToken).ConfigureAwait(false); - await GenerateWorkbookXmlAsync(cancellationToken).ConfigureAwait(false); - await GenerateContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task AddFilesToZipAsync(CancellationToken cancellationToken) - { - foreach (var item in _files) - { - cancellationToken.ThrowIfCancellationRequested(); - await CreateZipEntryAsync(item.Path, item.Byte, cancellationToken).ConfigureAwait(false); - } - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (var context = new SheetStyleBuildContext(_zipDictionary, _archive, _utf8WithBom, _configuration.DynamicColumns)) - { - ISheetStyleBuilder builder = null; - switch (_configuration.TableStyles) - { - case TableStyles.None: - builder = new MinimalSheetStyleBuilder(context); - break; - case TableStyles.Default: - builder = new DefaultSheetStyleBuilder(context, _configuration.StyleOptions); - break; - } - var result = await builder.BuildAsync(cancellationToken).ConfigureAwait(false); - _cellXfIdMap = result.CellXfIdMap; - } - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateDrawinRelXmlAsync(CancellationToken cancellationToken) - { - for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++) - { - cancellationToken.ThrowIfCancellationRequested(); - await GenerateDrawinRelXmlAsync(sheetIndex, cancellationToken).ConfigureAwait(false); - } - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - 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).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateDrawingXmlAsync(CancellationToken cancellationToken) - { - for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++) - { - cancellationToken.ThrowIfCancellationRequested(); - await GenerateDrawingXmlAsync(sheetIndex, cancellationToken).ConfigureAwait(false); - } - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - 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).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateWorkbookXmlAsync(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - GenerateWorkBookXmls( - out StringBuilder workbookXml, - out StringBuilder workbookRelsXml, - out Dictionary sheetsRelsXml); - - foreach (var sheetRelsXml in sheetsRelsXml) - { - await CreateZipEntryAsync( - ExcelFileNames.SheetRels(sheetRelsXml.Key), - null, - ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), - cancellationToken).ConfigureAwait(false); - } - - await CreateZipEntryAsync( - ExcelFileNames.Workbook, - ExcelContentTypes.Workbook, - ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), - cancellationToken).ConfigureAwait(false); - - await CreateZipEntryAsync( - ExcelFileNames.WorkbookRels, - null, - ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), - cancellationToken).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateContentTypesXmlAsync(CancellationToken cancellationToken) - { - var contentTypes = GetContentTypesXml(); - await CreateZipEntryAsync(ExcelFileNames.ContentTypes, null, contentTypes, cancellationToken).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var contentTypesZipEntry = _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.ContentTypes); - if (contentTypesZipEntry == null) - { - await GenerateContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); - return; - } -#if NET5_0_OR_GREATER -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using (var stream = contentTypesZipEntry.Open()) -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task -#else - using (var stream = contentTypesZipEntry.Open()) -#endif - { -#if NETCOREAPP2_0_OR_GREATER - var doc = await XDocument.LoadAsync(stream, LoadOptions.None, cancellationToken).ConfigureAwait(false); -#else - var doc = XDocument.Load(stream); -#endif - 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)) - { - var newElement = new XElement(ns + "Override", new XAttribute("ContentType", p.Value.ContentType), new XAttribute("PartName", partName)); - typesElement.Add(newElement); - } - } - - stream.Position = 0; -#if NETCOREAPP2_0_OR_GREATER - await doc.SaveAsync(stream, SaveOptions.None, cancellationToken).ConfigureAwait(false); -#else - doc.Save(stream); -#endif - } - } - - private async Task CreateZipEntryAsync(string path, string contentType, string content, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); - -#if NET5_0_OR_GREATER -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using (var zipStream = entry.Open()) -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task -#else - using (var zipStream = entry.Open()) -#endif - using (var writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken)) - await writer.WriteAsync(content).ConfigureAwait(false); - - if (!string.IsNullOrEmpty(contentType)) - _zipDictionary.Add(path, new ZipPackageInfo(entry, contentType)); - } - - private async Task CreateZipEntryAsync(string path, byte[] content, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); - -#if NET5_0_OR_GREATER -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using (var zipStream = entry.Open()) - await zipStream.WriteAsync(content, cancellationToken).ConfigureAwait(false); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task -#else - using (var zipStream = entry.Open()) - await zipStream.WriteAsync(content, 0, content.Length, cancellationToken).ConfigureAwait(false); -#endif - } - } -} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 679f628b..3de3b34c 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -11,6 +11,8 @@ using System.IO.Compression; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using System.Xml.Linq; namespace MiniExcelLibs.OpenXml @@ -45,54 +47,626 @@ public ExcelOpenXmlSheetWriter(Stream stream, object value, string sheetName, IC _defaultSheetName = sheetName; } - private int CreateSheetXml(object values, string sheetPath) + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task SaveAsAsync(CancellationToken cancellationToken = default) { + try + { + cancellationToken.ThrowIfCancellationRequested(); + + await GenerateDefaultOpenXmlAsync(cancellationToken).ConfigureAwait(false); + + var sheets = GetSheets(); + var rowsWritten = new List(); + + 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).ConfigureAwait(false); + rowsWritten.Add(rows); + } + + await GenerateEndXmlAsync(cancellationToken).ConfigureAwait(false); + return rowsWritten.ToArray(); + } + finally + { + _archive.Dispose(); + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) + { + try + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!_configuration.FastMode) + throw new InvalidOperationException("Insert requires fast mode to be enabled"); + + cancellationToken.ThrowIfCancellationRequested(); + + using var reader = await ExcelOpenXmlSheetReader.CreateAsync(_stream, _configuration, cancellationToken: cancellationToken).ConfigureAwait(false); + var sheetRecords = (await reader.GetWorkbookRelsAsync(_archive.Entries, cancellationToken).ConfigureAwait(false)).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).ConfigureAwait(false);//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 + + 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).ConfigureAwait(false); + } + else + { + _currentSheetIndex = existSheetDto.SheetIdx; + _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); + rowsWritten = await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken).ConfigureAwait(false); + } + + await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); + + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(_currentSheetIndex - 1))?.Delete(); + await GenerateDrawinRelXmlAsync(_currentSheetIndex - 1, cancellationToken).ConfigureAwait(false); + + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(_currentSheetIndex - 1))?.Delete(); + await GenerateDrawingXmlAsync(_currentSheetIndex - 1, cancellationToken).ConfigureAwait(false); + + 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).ConfigureAwait(false); + } + + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete(); + await CreateZipEntryAsync(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), cancellationToken).ConfigureAwait(false); + + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); + await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken).ConfigureAwait(false); + + await InsertContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); + + return rowsWritten; + } + finally + { + _archive.Dispose(); + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken) + { + await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken).ConfigureAwait(false); + await CreateZipEntryAsync(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString, cancellationToken).ConfigureAwait(false); + await GenerateStylesXmlAsync(cancellationToken).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task CreateSheetXmlAsync(object values, string sheetPath, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); var rowsWritten = 0; - + using (var zipStream = entry.Open()) using (var writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) { if (values == null) { - WriteEmptySheet(writer); + await WriteEmptySheetAsync(writer).ConfigureAwait(false); } else { - rowsWritten = WriteValues(writer, values); + rowsWritten = await WriteValuesAsync(writer, values, cancellationToken).ConfigureAwait(false); } } _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); return rowsWritten; } - private static void WriteEmptySheet(MiniExcelStreamWriter writer) + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async Task WriteEmptySheetAsync(MiniExcelStreamWriter writer) { - writer.Write(ExcelXml.EmptySheetXml); + await writer.WriteAsync(ExcelXml.EmptySheetXml).ConfigureAwait(false); } - private void CreateZipEntry(string path, string contentType, string content) + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async Task WriteDimensionPlaceholderAsync(MiniExcelStreamWriter writer) { - ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); - using (var zipStream = entry.Open()) - using (MiniExcelStreamWriter writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) + var dimensionPlaceholderPostition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.DimensionPlaceholder).ConfigureAwait(false); // end of code will be replaced + + return dimensionPlaceholderPostition; + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async Task WriteDimensionAsync(MiniExcelStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) + { + // Flush and save position so that we can get back again. + var position = await writer.FlushAsync().ConfigureAwait(false); + + writer.SetPosition(placeholderPosition); + await writer.WriteAndFlushAsync($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}""").ConfigureAwait(false); + + writer.SetPosition(position); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task WriteValuesAsync(MiniExcelStreamWriter writer, object values, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + IMiniExcelWriteAdapter writeAdapter = null; + if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) { - writer.Write(content); + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); } - if (!string.IsNullOrEmpty(contentType)) + var count = 0; + var isKnownCount = writeAdapter != null && writeAdapter.TryGetKnownCount(out count); + List props; +#if SYNC_ONLY + props = writeAdapter?.GetColumns(); +#else + props = writeAdapter != null ? writeAdapter?.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); +#endif + if (props == null) { - _zipDictionary.Add(path, new ZipPackageInfo(entry, contentType)); + await WriteEmptySheetAsync(writer).ConfigureAwait(false); + return 0; + } + int maxRowIndex; + var maxColumnIndex = props.Count(x => x != null && !x.ExcelIgnore); + + await writer.WriteAsync(WorksheetXml.StartWorksheetWithRelationship, cancellationToken).ConfigureAwait(false); + + long dimensionPlaceholderPostition = 0; + + // We can write the dimensions directly if the row count is known + if (isKnownCount) + { + maxRowIndex = _printHeader ? count + 1 : count; + await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count)), cancellationToken).ConfigureAwait(false); + } + else if (_configuration.FastMode) + { + dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer).ConfigureAwait(false); + } + + //sheet view + await writer.WriteAsync(GetSheetViews(), cancellationToken).ConfigureAwait(false); + + //cols:width + ExcelWidthCollection widths = null; + long columnWidthsPlaceholderPosition = 0; + if (_configuration.EnableAutoWidth) + { + columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, maxColumnIndex, cancellationToken).ConfigureAwait(false); + widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); + } + else + { + await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken).ConfigureAwait(false); + } + + //header + await writer.WriteAsync(WorksheetXml.StartSheetData, cancellationToken).ConfigureAwait(false); + var currentRowIndex = 0; + if (_printHeader) + { + await PrintHeaderAsync(writer, props, cancellationToken).ConfigureAwait(false); + currentRowIndex++; } + + if (writeAdapter != null) + { + foreach (var row in writeAdapter.GetRows(props, cancellationToken)) + { + cancellationToken.ThrowIfCancellationRequested(); + + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex), cancellationToken).ConfigureAwait(false); + foreach (var cellValue in row) + { + cancellationToken.ThrowIfCancellationRequested(); + await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); + } + await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); + } + } + else + { +#if !SYNC_ONLY + await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex), cancellationToken).ConfigureAwait(false); + + await foreach (var cellValue in row.ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); + } + await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); + } +#endif + } + maxRowIndex = currentRowIndex; + + await writer.WriteAsync(WorksheetXml.EndSheetData, cancellationToken).ConfigureAwait(false); + + if (_configuration.AutoFilter) + { + await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex)), cancellationToken).ConfigureAwait(false); + } + + await writer.WriteAsync(WorksheetXml.Drawing(_currentSheetIndex), cancellationToken).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.EndWorksheet, cancellationToken).ConfigureAwait(false); + + if (_configuration.FastMode && dimensionPlaceholderPostition != 0) + { + await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition).ConfigureAwait(false); + } + if (_configuration.EnableAutoWidth) + { + await OverwriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths?.Columns, cancellationToken).ConfigureAwait(false); + } + + if (_printHeader) + maxRowIndex--; + + return maxRowIndex; } - private void CreateZipEntry(string path, byte[] content) + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async Task WriteColumnWidthPlaceholdersAsync(MiniExcelStreamWriter writer, int count, CancellationToken cancellationToken = default) { - ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); - using (var zipStream = entry.Open()) + cancellationToken.ThrowIfCancellationRequested(); + + var placeholderPosition = await writer.FlushAsync().ConfigureAwait(false); + await writer.WriteWhitespaceAsync(WorksheetXml.GetColumnPlaceholderLength(count)).ConfigureAwait(false); + return placeholderPosition; + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async Task OverwriteColumnWidthPlaceholdersAsync(MiniExcelStreamWriter writer, long placeholderPosition, IEnumerable columnWidths, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var position = await writer.FlushAsync().ConfigureAwait(false); + + writer.SetPosition(placeholderPosition); + await WriteColumnsWidthsAsync(writer, columnWidths, cancellationToken).ConfigureAwait(false); + + await writer.FlushAsync().ConfigureAwait(false); + writer.SetPosition(position); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private static async Task WriteColumnsWidthsAsync(MiniExcelStreamWriter writer, IEnumerable columnWidths, CancellationToken cancellationToken = default) + { + var hasWrittenStart = false; + foreach (var column in columnWidths) { - zipStream.Write(content, 0, content.Length); + cancellationToken.ThrowIfCancellationRequested(); + + if (!hasWrittenStart) + { + await writer.WriteAsync(WorksheetXml.StartCols, cancellationToken).ConfigureAwait(false); + hasWrittenStart = true; + } + await writer.WriteAsync(WorksheetXml.Column(column.Index, column.Width), cancellationToken).ConfigureAwait(false); + } + + if (!hasWrittenStart) + return; + + await writer.WriteAsync(WorksheetXml.EndCols, cancellationToken).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task PrintHeaderAsync(MiniExcelStreamWriter writer, List props, CancellationToken cancellationToken = default) + { + const int yIndex = 1; + await writer.WriteAsync(WorksheetXml.StartRow(yIndex), cancellationToken).ConfigureAwait(false); + + var xIndex = 1; + foreach (var p in props) + { + //reason : https://github.com/mini-software/MiniExcel/issues/142 + if (p != null) + { + if (p.ExcelIgnore) + continue; + + var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex); + await WriteCellAsync(writer, r, columnName: p.ExcelColumnName).ConfigureAwait(false); + } + xIndex++; + } + + await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task WriteCellAsync(MiniExcelStreamWriter writer, string cellReference, string columnName) + { + await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", GetCellXfId("1"), ExcelOpenXmlUtils.EncodeXML(columnName))).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task WriteCellAsync(MiniExcelStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, ExcelWidthCollection widthCollection) + { + if (columnInfo?.CustomFormatter != null) + { + try + { + value = columnInfo.CustomFormatter(value); + } + catch + { + //ignored + } + } + + var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); + var valueIsNull = value is null || + value is DBNull || + (_configuration.WriteEmptyStringAsNull && value is string vs && vs == string.Empty); + + if (_configuration.EnableWriteNullValueCell && valueIsNull) + { + await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2"))).ConfigureAwait(false); + return; + } + + var tuple = GetCellValue(rowIndex, cellIndex, value, columnInfo, valueIsNull); + + var styleIndex = tuple.Item1; + var dataType = tuple.Item2; + var cellValue = tuple.Item3; + var columnType = columnInfo.ExcelColumnType; + + /*Prefix and suffix blank space will lost after SaveAs #294*/ + var preserveSpace = cellValue != null && (cellValue.StartsWith(" ", StringComparison.Ordinal) || + cellValue.EndsWith(" ", StringComparison.Ordinal)); + + await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, GetCellXfId(styleIndex), cellValue, preserveSpace: preserveSpace, columnType: columnType)).ConfigureAwait(false); + widthCollection?.AdjustWidth(cellIndex, cellValue); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task GenerateEndXmlAsync(CancellationToken cancellationToken) + { + await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); + await GenerateDrawinRelXmlAsync(cancellationToken).ConfigureAwait(false); + await GenerateDrawingXmlAsync(cancellationToken).ConfigureAwait(false); + await GenerateWorkbookXmlAsync(cancellationToken).ConfigureAwait(false); + await GenerateContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task AddFilesToZipAsync(CancellationToken cancellationToken) + { + foreach (var item in _files) + { + cancellationToken.ThrowIfCancellationRequested(); + await CreateZipEntryAsync(item.Path, item.Byte, cancellationToken).ConfigureAwait(false); + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + using (var context = new SheetStyleBuildContext(_zipDictionary, _archive, _utf8WithBom, _configuration.DynamicColumns)) + { + ISheetStyleBuilder builder = null; + switch (_configuration.TableStyles) + { + case TableStyles.None: + builder = new MinimalSheetStyleBuilder(context); + break; + case TableStyles.Default: + builder = new DefaultSheetStyleBuilder(context, _configuration.StyleOptions); + break; + } + var result = await builder.BuildAsync(cancellationToken).ConfigureAwait(false); + _cellXfIdMap = result.CellXfIdMap; + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task GenerateDrawinRelXmlAsync(CancellationToken cancellationToken) + { + for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++) + { + cancellationToken.ThrowIfCancellationRequested(); + await GenerateDrawinRelXmlAsync(sheetIndex, cancellationToken).ConfigureAwait(false); } } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + 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).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task GenerateDrawingXmlAsync(CancellationToken cancellationToken) + { + for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++) + { + cancellationToken.ThrowIfCancellationRequested(); + await GenerateDrawingXmlAsync(sheetIndex, cancellationToken).ConfigureAwait(false); + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + 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).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task GenerateWorkbookXmlAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + GenerateWorkBookXmls( + out StringBuilder workbookXml, + out StringBuilder workbookRelsXml, + out Dictionary sheetsRelsXml); + + foreach (var sheetRelsXml in sheetsRelsXml) + { + await CreateZipEntryAsync( + ExcelFileNames.SheetRels(sheetRelsXml.Key), + null, + ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), + cancellationToken).ConfigureAwait(false); + } + + await CreateZipEntryAsync( + ExcelFileNames.Workbook, + ExcelContentTypes.Workbook, + ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), + cancellationToken).ConfigureAwait(false); + + await CreateZipEntryAsync( + ExcelFileNames.WorkbookRels, + null, + ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), + cancellationToken).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task GenerateContentTypesXmlAsync(CancellationToken cancellationToken) + { + var contentTypes = GetContentTypesXml(); + await CreateZipEntryAsync(ExcelFileNames.ContentTypes, null, contentTypes, cancellationToken).ConfigureAwait(false); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var contentTypesZipEntry = _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.ContentTypes); + if (contentTypesZipEntry == null) + { + await GenerateContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); + return; + } +#if NET5_0_OR_GREATER +#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + await using (var stream = contentTypesZipEntry.Open()) +#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task +#else + using (var stream = contentTypesZipEntry.Open()) +#endif + { +#if NETCOREAPP2_0_OR_GREATER + var doc = await XDocument.LoadAsync(stream, LoadOptions.None, cancellationToken).ConfigureAwait(false); +#else + var doc = XDocument.Load(stream); +#endif + 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)) + { + var newElement = new XElement(ns + "Override", new XAttribute("ContentType", p.Value.ContentType), new XAttribute("PartName", partName)); + typesElement.Add(newElement); + } + } + + stream.Position = 0; +#if NETCOREAPP2_0_OR_GREATER + await doc.SaveAsync(stream, SaveOptions.None, cancellationToken).ConfigureAwait(false); +#else + doc.Save(stream); +#endif + } + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task CreateZipEntryAsync(string path, string contentType, string content, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); + +#if NET5_0_OR_GREATER +#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + await using (var zipStream = entry.Open()) +#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task +#else + using (var zipStream = entry.Open()) +#endif + using (var writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) + await writer.WriteAsync(content, cancellationToken).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(contentType)) + _zipDictionary.Add(path, new ZipPackageInfo(entry, contentType)); + } + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task CreateZipEntryAsync(string path, byte[] content, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); + +#if NET5_0_OR_GREATER +#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + await using (var zipStream = entry.Open()) + await zipStream.WriteAsync(content, cancellationToken).ConfigureAwait(false); +#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task +#else + using (var zipStream = entry.Open()) + await zipStream.WriteAsync(content, 0, content.Length, cancellationToken).ConfigureAwait(false); +#endif + } } } diff --git a/src/MiniExcel/OpenXml/MiniExcelAsyncStreamWriter.cs b/src/MiniExcel/OpenXml/MiniExcelAsyncStreamWriter.cs deleted file mode 100644 index fb28e3e0..00000000 --- a/src/MiniExcel/OpenXml/MiniExcelAsyncStreamWriter.cs +++ /dev/null @@ -1,77 +0,0 @@ -// -// Copyright (c) 2024 Rolls-Royce plc -// - -namespace MiniExcelLibs.OpenXml -{ - using System; - using System.IO; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - - internal class MiniExcelAsyncStreamWriter : IDisposable - { - private readonly Stream _stream; - private readonly Encoding _encoding; - private readonly CancellationToken _cancellationToken; - private readonly StreamWriter _streamWriter; - private bool _disposedValue; - - public MiniExcelAsyncStreamWriter(Stream stream, Encoding encoding, int bufferSize, CancellationToken cancellationToken) - { - _stream = stream; - _encoding = encoding; - _cancellationToken = cancellationToken; - _streamWriter = new StreamWriter(stream, _encoding, bufferSize); - } - public async Task WriteAsync(string content) - { - _cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrEmpty(content)) - return; - await _streamWriter.WriteAsync(content).ConfigureAwait(false); - } - - public async Task WriteAndFlushAsync(string content) - { - await WriteAsync(content).ConfigureAwait(false); - return await FlushAsync().ConfigureAwait(false); - } - - public async Task WriteWhitespaceAsync(int length) - { - await _streamWriter.WriteAsync(new string(' ', length)).ConfigureAwait(false); - } - - public async Task FlushAsync() - { - _cancellationToken.ThrowIfCancellationRequested(); - - await _streamWriter.FlushAsync().ConfigureAwait(false); - return _streamWriter.BaseStream.Position; - } - - public void SetPosition(long position) - { - _streamWriter.BaseStream.Position = position; - } - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - _streamWriter?.Dispose(); - _disposedValue = true; - } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs b/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs index 4a5f1c58..e7088583 100644 --- a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs +++ b/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs @@ -1,45 +1,50 @@ using System; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace MiniExcelLibs.OpenXml { - internal class MiniExcelStreamWriter : IDisposable + internal partial class MiniExcelStreamWriter : IDisposable { - private readonly Stream _stream; - private readonly Encoding _encoding; private readonly StreamWriter _streamWriter; - private bool disposedValue; - + private bool _disposedValue; + public MiniExcelStreamWriter(Stream stream, Encoding encoding, int bufferSize) { - _stream = stream; - _encoding = encoding; - _streamWriter = new StreamWriter(stream, _encoding, bufferSize); + _streamWriter = new StreamWriter(stream, encoding, bufferSize); } - public void Write(string content) + + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task WriteAsync(string content, CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); + if (string.IsNullOrEmpty(content)) return; - - _streamWriter.Write(content); + await _streamWriter.WriteAsync(content).ConfigureAwait(false); } - public long WriteAndFlush(string content) + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task WriteAndFlushAsync(string content, CancellationToken cancellationToken = default) { - Write(content); - _streamWriter.Flush(); - return _streamWriter.BaseStream.Position; + cancellationToken.ThrowIfCancellationRequested(); + + await WriteAsync(content, cancellationToken).ConfigureAwait(false); + return await FlushAsync().ConfigureAwait(false); } - public void WriteWhitespace(int length) + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task WriteWhitespaceAsync(int length) { - _streamWriter.Write(new string(' ', length)); + await _streamWriter.WriteAsync(new string(' ', length)).ConfigureAwait(false); } - public long Flush() + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task FlushAsync() { - _streamWriter.Flush(); + await _streamWriter.FlushAsync().ConfigureAwait(false); return _streamWriter.BaseStream.Position; } @@ -50,10 +55,10 @@ public void SetPosition(long position) protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { _streamWriter?.Dispose(); - disposedValue = true; + _disposedValue = true; } } @@ -64,4 +69,4 @@ public void Dispose() GC.SuppressFinalize(this); } } -} +} \ No newline at end of file From a87fb35b67bcc483c9dde2c31b391a8753f596fb Mon Sep 17 00:00:00 2001 From: izanhzh Date: Tue, 17 Jun 2025 10:46:37 +0800 Subject: [PATCH 2/5] refactor `CsvWriter` --- src/MiniExcel/Csv/CsvWriter.cs | 108 ++++++++------------------------- 1 file changed, 26 insertions(+), 82 deletions(-) diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel/Csv/CsvWriter.cs index f7af71a6..24cb56f6 100644 --- a/src/MiniExcel/Csv/CsvWriter.cs +++ b/src/MiniExcel/Csv/CsvWriter.cs @@ -11,7 +11,7 @@ namespace MiniExcelLibs.Csv { - internal class CsvWriter : IExcelWriter, IDisposable + internal partial class CsvWriter : IExcelWriter, IDisposable { private readonly StreamWriter _writer; private readonly CsvConfiguration _configuration; @@ -27,26 +27,6 @@ public CsvWriter(Stream stream, object value, IConfiguration configuration, bool _writer = _configuration.StreamWriterFunc(stream); } - public int[] SaveAs() - { - if (_value == null) - { - _writer.Write(""); - _writer.Flush(); - return new int[0]; - } - - var rowsWritten = WriteValues(_value); - _writer.Flush(); - - return new[] { rowsWritten }; - } - - public int Insert(bool overwriteSheet = false) - { - return SaveAs().FirstOrDefault(); - } - private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column) { rowBuilder.Append(CsvHelpers.ConvertToCsvValue(ToCsvString(column.Value, column.Prop), _configuration)); @@ -57,7 +37,7 @@ private static void RemoveTrailingSeparator(StringBuilder rowBuilder) { if (rowBuilder.Length == 0) return; - + rowBuilder.Remove(rowBuilder.Length - 1, 1); } @@ -65,61 +45,23 @@ private string GetHeader(List props) => string.Join( _configuration.Seperator.ToString(), props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration))); - private int WriteValues(object values) - { - var writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - - var props = writeAdapter.GetColumns(); - if (props == null) - { - _writer.Write(_configuration.NewLine); - _writer.Flush(); - return 0; - } - - if (_printHeader) - { - _writer.Write(GetHeader(props)); - _writer.Write(_configuration.NewLine); - } - - if (writeAdapter == null) - return 0; - - var rowBuilder = new StringBuilder(); - var rowsWritten = 0; - - foreach (var row in writeAdapter.GetRows(props)) - { - rowBuilder.Clear(); - foreach (var column in row) - { - AppendColumn(rowBuilder, column); - } - RemoveTrailingSeparator(rowBuilder); - _writer.Write(rowBuilder.ToString()); - _writer.Write(_configuration.NewLine); - - rowsWritten++; - } - return rowsWritten; - } - + [Zomp.SyncMethodGenerator.CreateSyncVersion] 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)) { writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); } - var props = writeAdapter != null ? writeAdapter.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); + List props; +#if SYNC_ONLY + props = writeAdapter?.GetColumns(); #else - IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - var props = writeAdapter.GetColumns(); + props = writeAdapter != null ? writeAdapter.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); #endif + if (props == null) { await _writer.WriteAsync(_configuration.NewLine @@ -134,7 +76,7 @@ await _writer.FlushAsync( ).ConfigureAwait(false); return 0; } - + if (_printHeader) { await _writer.WriteAsync(GetHeader(props) @@ -148,10 +90,10 @@ await _writer.WriteAsync(newLine #endif ).ConfigureAwait(false); } - + var rowBuilder = new StringBuilder(); var rowsWritten = 0; - + if (writeAdapter != null) { foreach (var row in writeAdapter.GetRows(props, cancellationToken)) @@ -162,7 +104,7 @@ await _writer.WriteAsync(newLine cancellationToken.ThrowIfCancellationRequested(); AppendColumn(rowBuilder, column); } - + RemoveTrailingSeparator(rowBuilder); await _writer.WriteAsync(rowBuilder.ToString() #if NET5_0_OR_GREATER @@ -174,24 +116,24 @@ await _writer.WriteAsync(newLine .AsMemory(), cancellationToken #endif ).ConfigureAwait(false); - + rowsWritten++; } } -#if NETSTANDARD2_0_OR_GREATER || NET else { +#if !SYNC_ONLY await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); rowBuilder.Clear(); - + await foreach (var column in row.ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); AppendColumn(rowBuilder, column); } - + RemoveTrailingSeparator(rowBuilder); await _writer.WriteAsync(rowBuilder.ToString() #if NET5_0_OR_GREATER @@ -203,18 +145,19 @@ await _writer.WriteAsync(newLine .AsMemory(), cancellationToken #endif ).ConfigureAwait(false); - + rowsWritten++; } - } #endif + } return rowsWritten; } + [Zomp.SyncMethodGenerator.CreateSyncVersion] public async Task SaveAsAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - + var seperator = _configuration.Seperator.ToString(); var newLine = _configuration.NewLine; @@ -239,10 +182,11 @@ await _writer.FlushAsync( cancellationToken #endif ).ConfigureAwait(false); - + return new[] { rowsWritten }; } + [Zomp.SyncMethodGenerator.CreateSyncVersion] public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) { var rowsWritten = await SaveAsAsync(cancellationToken).ConfigureAwait(false); @@ -260,11 +204,11 @@ 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) + 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); From bbe5a48869a2963600f553be67eb0bc3699da9a4 Mon Sep 17 00:00:00 2001 From: Victor Irzak Date: Mon, 16 Jun 2025 22:52:03 -0400 Subject: [PATCH 3/5] Add cancellation token to FlushAsync --- src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs b/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs index e7088583..05c2a3f0 100644 --- a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs +++ b/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs @@ -32,7 +32,7 @@ public async Task WriteAndFlushAsync(string content, CancellationToken can cancellationToken.ThrowIfCancellationRequested(); await WriteAsync(content, cancellationToken).ConfigureAwait(false); - return await FlushAsync().ConfigureAwait(false); + return await FlushAsync(cancellationToken).ConfigureAwait(false); } [Zomp.SyncMethodGenerator.CreateSyncVersion] @@ -42,9 +42,13 @@ public async Task WriteWhitespaceAsync(int length) } [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task FlushAsync() + public async Task FlushAsync(CancellationToken cancellationToken = default) { - await _streamWriter.FlushAsync().ConfigureAwait(false); + await _streamWriter.FlushAsync( +#if NET8_0_OR_GREATER + cancellationToken +#endif + ).ConfigureAwait(false); return _streamWriter.BaseStream.Position; } From d604cbb28f3f622cfeeb7f96e8739bd149a754f1 Mon Sep 17 00:00:00 2001 From: Victor Irzak Date: Mon, 16 Jun 2025 22:56:02 -0400 Subject: [PATCH 4/5] Fix compilation --- src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 3de3b34c..69fc611d 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -341,7 +341,7 @@ private static async Task WriteColumnWidthPlaceholdersAsync(MiniExcelStrea { cancellationToken.ThrowIfCancellationRequested(); - var placeholderPosition = await writer.FlushAsync().ConfigureAwait(false); + var placeholderPosition = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); await writer.WriteWhitespaceAsync(WorksheetXml.GetColumnPlaceholderLength(count)).ConfigureAwait(false); return placeholderPosition; } @@ -351,12 +351,12 @@ private static async Task OverwriteColumnWidthPlaceholdersAsync(MiniExcelStreamW { cancellationToken.ThrowIfCancellationRequested(); - var position = await writer.FlushAsync().ConfigureAwait(false); + var position = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); writer.SetPosition(placeholderPosition); await WriteColumnsWidthsAsync(writer, columnWidths, cancellationToken).ConfigureAwait(false); - await writer.FlushAsync().ConfigureAwait(false); + await writer.FlushAsync(cancellationToken).ConfigureAwait(false); writer.SetPosition(position); } From fddfe5bdfb8c3e1edcd085ab1394ccd61c66bf23 Mon Sep 17 00:00:00 2001 From: izanhzh Date: Tue, 17 Jun 2025 11:27:36 +0800 Subject: [PATCH 5/5] minor changes --- src/MiniExcel/Csv/CsvWriter.cs | 2 +- src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel/Csv/CsvWriter.cs index 24cb56f6..f4318639 100644 --- a/src/MiniExcel/Csv/CsvWriter.cs +++ b/src/MiniExcel/Csv/CsvWriter.cs @@ -46,7 +46,7 @@ private string GetHeader(List props) => string.Join( props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration))); [Zomp.SyncMethodGenerator.CreateSyncVersion] - 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 = default) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs b/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs index 05c2a3f0..f6cc53a0 100644 --- a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs +++ b/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs @@ -9,7 +9,7 @@ namespace MiniExcelLibs.OpenXml internal partial class MiniExcelStreamWriter : IDisposable { private readonly StreamWriter _streamWriter; - private bool _disposedValue; + private bool disposedValue; public MiniExcelStreamWriter(Stream stream, Encoding encoding, int bufferSize) { @@ -59,10 +59,10 @@ public void SetPosition(long position) protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + if (!disposedValue) { _streamWriter?.Dispose(); - _disposedValue = true; + disposedValue = true; } }