diff --git "a/doc/\347\211\210\346\234\254\346\227\245\345\277\227.md" "b/doc/\347\211\210\346\234\254\346\227\245\345\277\227.md" index e6cbdb8..c0b1093 100644 --- "a/doc/\347\211\210\346\234\254\346\227\245\345\277\227.md" +++ "b/doc/\347\211\210\346\234\254\346\227\245\345\277\227.md" @@ -1,5 +1,10 @@ # 版本功能更新记录 +## v2.1.4 +- ⚡️增加异步扩展方法`WriteExcelAsync`, 接收`IAsyncEnumerable`的数据 +- 🐞修复DateTime.ToOADate的显示问题 +- 🛠代码重构 + ## v2.1.3 - 🐞读取行时,校验单元格的位置,确保索引正确 diff --git a/src/LightExcel/ExcelConfiguration.cs b/src/LightExcel/ExcelConfiguration.cs index 9c5fcaf..437c6a0 100644 --- a/src/LightExcel/ExcelConfiguration.cs +++ b/src/LightExcel/ExcelConfiguration.cs @@ -6,10 +6,12 @@ namespace LightExcel public class TransConfiguration { public bool SheetNumberFormat { get; set; } + internal ExcelConfiguration ExcelConfig { get; set; } public Func NumberFormatColumnFilter { get; set; } - public TransConfiguration() + public TransConfiguration(ExcelConfiguration configuration) { NumberFormatColumnFilter = col => SheetNumberFormat; + ExcelConfig = configuration; } } diff --git a/src/LightExcel/ExcelHelper.Stream.cs b/src/LightExcel/ExcelHelper.Stream.cs index c2a7719..4bcd4fd 100644 --- a/src/LightExcel/ExcelHelper.Stream.cs +++ b/src/LightExcel/ExcelHelper.Stream.cs @@ -1,5 +1,6 @@ using LightExcel.OpenXml; using LightExcel.TypedDeserializer; +using LightExcel.Utils; using System; using System.Collections.Generic; using System.IO; @@ -9,29 +10,25 @@ namespace LightExcel { - public partial class ExcelHelper + internal partial class ExcelHelper { - - public void WriteExcel(Stream stream, object data, string sheetName = "Sheet1", Action? config = null) + public void WriteExcel(IDataRender render, Stream stream, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null) { - config?.Invoke(configuration); - using var trans = new TransExcelHelper(stream, configuration); - trans.WriteExcel(data, sheetName); - } - public void WriteExcelByTemplate(Stream stream, Stream templateStream, object data, string sheetName = "Sheet1", Action? config = null) - { - config?.Invoke(configuration); - using var doc = ExcelDocument.CreateByTemplate(stream, templateStream, configuration); - HandleWriteTemplate(doc, data, sheetName); + config ??= new(); + using var trans = new TransExcelHelper(stream, config); + trans.WriteExcel(render, data, sheetName); } + public ITransactionExcelHelper BeginTransaction(Stream stream, Action? config = null) { + ExcelConfiguration configuration = new(); config?.Invoke(configuration); return new TransExcelHelper(stream, configuration); } public IExcelDataReader ReadExcel(Stream stream, string? sheetName = null, Action? config = null) { + ExcelConfiguration configuration = new(); config?.Invoke(configuration); var archive = ExcelDocument.Open(stream, configuration); return new ExcelReader(archive, configuration, sheetName); diff --git a/src/LightExcel/ExcelHelper.Template.cs b/src/LightExcel/ExcelHelper.Template.cs index 9522e1b..25bb557 100644 --- a/src/LightExcel/ExcelHelper.Template.cs +++ b/src/LightExcel/ExcelHelper.Template.cs @@ -9,74 +9,26 @@ namespace LightExcel { - public partial class ExcelHelper + internal partial class ExcelHelper { - /// - /// 仅支持第一个sheet - /// - /// - /// - /// - /// - /// - internal void HandleWriteTemplate(ExcelArchiveEntry doc, object data, string sheetName) + public void WriteExcelByTemplate(IDataRender render, ExcelArchiveEntry doc, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null) { - configuration.FillByTemplate = true; - - // 获取sheet对象 - var sheet = doc.WorkBook.WorkSheets.FirstOrDefault() ?? throw new Exception("read excel sheet failed"); - // 获取最后一行当模板 - var header = sheet.ToList(); - var templateRow = header.Last(); - // 获取共享字符串列表 - var sst = doc.WorkBook.SharedStrings?.ToList(); - var render = RenderProvider.GetDataRender(data.GetType(), configuration); - ExcelColumnInfo[]? columns = configuration.FillWithPlacholder ? [.. CollectExcelColumnInfos(templateRow, sst)] : [.. render.CollectExcelColumnInfo(data)]; - sheet.Columns = columns; - if (configuration.FillWithPlacholder) - { - templateRow.IsTemplateRow = true; - configuration.StartRowIndex = templateRow.RowIndex - 1; - } - else - { - configuration.StartRowIndex = templateRow.RowIndex; - } - var newRows = render.RenderBody(data, sheet, columns, new TransConfiguration { SheetNumberFormat = configuration.AddSheetNumberFormat }); - sheet.Replace(header.Concat(newRows)); - doc.Save(); + config ??= new ExcelConfiguration(); + TransExcelHelper.WriteByTemplate(render, doc, data, sheetName, config); } -#if NET8_0_OR_GREATER - [GeneratedRegex("{{(.+)}}")] - private static partial Regex ExtractColumn(); -#else - static readonly Regex extract = new("{{(.+)}}"); - private static Regex ExtractColumn() => extract; -#endif - private static IEnumerable CollectExcelColumnInfos(Row templateRow, List? sst) + public void WriteExcelByTemplate(IDataRender render, string path, string template, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null) { - foreach (var cell in templateRow.Children) - { - string? name = cell.Value; - var (X, Y) = ReferenceHelper.ConvertCellReferenceToXY(cell.Reference); - if (cell.Type == "s") - { - if (int.TryParse(name, out var s) && sst!.Count > s) - { - name = sst[s].Content; - } - } - if (name != null) - { - var match = ExtractColumn().Match(name); - if (match.Success) - { - name = match.Groups[1].Value; - var col = new ExcelColumnInfo(name) { ColumnIndex = X ?? 0, StyleIndex = cell.StyleIndex }; - yield return col; - } - } - } + config ??= new(); + using var doc = ExcelDocument.CreateByTemplate(path, template, config); + TransExcelHelper.WriteByTemplate(render, doc, data, sheetName, config); } + + public void WriteExcelByTemplate(IDataRender render, Stream stream, Stream templateStream, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null) + { + config ??= new(); + using var doc = ExcelDocument.CreateByTemplate(stream, templateStream, config); + TransExcelHelper.WriteByTemplate(render, doc, data, sheetName, config); + } + } } diff --git a/src/LightExcel/ExcelHelper.cs b/src/LightExcel/ExcelHelper.cs index 8b910dd..0a60693 100644 --- a/src/LightExcel/ExcelHelper.cs +++ b/src/LightExcel/ExcelHelper.cs @@ -7,12 +7,13 @@ namespace LightExcel { - public partial class ExcelHelper : IExcelHelper + internal partial class ExcelHelper : IExcelHelper { - private readonly ExcelConfiguration configuration = new ExcelConfiguration(); - + //public ExcelConfiguration configuration { get; set; } = new(); + #region 读取 public IExcelDataReader ReadExcel(string path, string? sheetName = null, Action? config = null) { + ExcelConfiguration configuration = new ExcelConfiguration(); config?.Invoke(configuration); var archive = ExcelDocument.Open(path, configuration); return new ExcelReader(archive, configuration, sheetName); @@ -49,29 +50,25 @@ public IEnumerable QueryExcel(string path, string? sheetName, Action? config = null) - { - if (File.Exists(path)) File.Delete(path); - config?.Invoke(configuration); - using var trans = new TransExcelHelper(path, configuration); - trans.WriteExcel(data, sheetName); - } - public void WriteExcelByTemplate(string path, string template, object data, string sheetName = "Sheet1", Action? config = null) + #region 写入 + + public void WriteExcel(IDataRender render, string path, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null) { + config ??= new(); if (File.Exists(path)) File.Delete(path); - config?.Invoke(configuration); - using var doc = ExcelDocument.CreateByTemplate(path, template, configuration); - HandleWriteTemplate(doc, data, sheetName); + using var trans = new TransExcelHelper(path, config); + trans.WriteExcel(render, data, sheetName); } - public ITransactionExcelHelper BeginTransaction(string path, Action? config = null) + public ITransactionExcelHelper BeginTransaction(ExcelArchiveEntry doc, ExcelConfiguration? config = null) { - if (File.Exists(path)) File.Delete(path); - config?.Invoke(configuration); - return new TransExcelHelper(path, configuration); + config ??= new(); + return new TransExcelHelper(doc, config); } + #endregion } } diff --git a/src/LightExcel/Extensions/ExcelHeperExtensions.DIctionary.cs b/src/LightExcel/Extensions/ExcelHeperExtensions.DIctionary.cs new file mode 100644 index 0000000..642c18d --- /dev/null +++ b/src/LightExcel/Extensions/ExcelHeperExtensions.DIctionary.cs @@ -0,0 +1,132 @@ +using LightExcel.OpenXml; +using LightExcel.Renders; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LightExcel; + +public static partial class ExcelHeperExtensions +{ + #region DictionaryRender - Dictionary作为数据源 + + /// + /// ]]>数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this IExcelHelper helper, string path, IEnumerable> datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcel(helper, path, datas, sheetName, config); + } + + /// + /// ]]>数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, string path, string template, IEnumerable> datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, path, template, datas, sheetName, config); + } + + /// + /// ]]>数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this IExcelHelper helper, Stream stream, IEnumerable> datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcel(helper, stream, datas, sheetName, config); + } + + /// + /// ]]>数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, Stream stream, Stream template, IEnumerable> datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, stream, template, datas, sheetName, config); + } + + /// + /// ]]>数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, string path, Stream templateStream, IEnumerable> datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, path, templateStream, datas, sheetName, config); + } + + /// + /// ]]>数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, Stream stream, string template, IEnumerable> datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, stream, template, datas, sheetName, config); + } + + #endregion + + #region 异步 + +#if NET6_0_OR_GREATER + + public static async Task WriteExcelAsync(this IExcelHelper helper, string path, IAsyncEnumerable> data, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + using var trans = helper.BeginTransaction(path, config); + //var render = new AsyncEnumerableEntityRender(configuration); + await trans.WriteExcelAsync(data, sheetName, cancellationToken: cancellationToken); + } + + public static async Task WriteExcelAsync(this IExcelHelper helper, Stream stream, IAsyncEnumerable> data, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + using var trans = helper.BeginTransaction(stream, config); + //var render = new AsyncEnumerableEntityRender(configuration); + await trans.WriteExcelAsync(data, sheetName, cancellationToken: cancellationToken); + } + + /// + /// 实体数据-使用模板文件 + /// + /// + /// + /// + /// + /// + /// + public static async Task WriteExcelByTemplateAsync(this IExcelHelper helper, string path, string template, IAsyncEnumerable> datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + var configuration = new ExcelConfiguration(); + config?.Invoke(configuration); + using var doc = ExcelDocument.CreateByTemplate(path, template, configuration); + await TransExcelHelper.WriteByTemplateAsync(doc, datas, sheetName, configuration, cancellationToken); + } +#endif + + #endregion +} diff --git a/src/LightExcel/Extensions/ExcelHeperExtensions.DataReader.cs b/src/LightExcel/Extensions/ExcelHeperExtensions.DataReader.cs new file mode 100644 index 0000000..532b164 --- /dev/null +++ b/src/LightExcel/Extensions/ExcelHeperExtensions.DataReader.cs @@ -0,0 +1,154 @@ +using LightExcel.OpenXml; +using LightExcel.Renders; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LightExcel; + +public static partial class ExcelHeperExtensions +{ + #region DataReaderRender - IDataReader作为数据源 + + /// + /// IDataReader数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this IExcelHelper helper, string path, IDataReader datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcel(helper, path, datas, sheetName, config); + } + + /// + /// IDataReader数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, string path, string template, IDataReader datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, path, template, datas, sheetName, config); + } + + /// + /// IDataReader数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this IExcelHelper helper, Stream stream, IDataReader datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcel(helper, stream, datas, sheetName, config); + } + + /// + /// IDataReader数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, Stream stream, Stream template, IDataReader datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, stream, template, datas, sheetName, config); + } + + /// + /// IDataReader数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, string path, Stream templateStream, IDataReader datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, path, templateStream, datas, sheetName, config); + } + + /// + /// IDataReader数据源 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, Stream stream, string template, IDataReader datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, stream, template, datas, sheetName, config); + } + #endregion + + #region 异步 + +#if NET6_0_OR_GREATER + + /// + /// 实体数据-保存到文件 + /// + /// + /// + /// + /// + /// + /// + public static async Task WriteExcelAsync(this IExcelHelper helper, string path, DbDataReader datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + using var trans = helper.BeginTransaction(path, config); + //var render = new AsyncEnumerableEntityRender(configuration); + await trans.WriteExcelAsync(datas, sheetName, cancellationToken: cancellationToken); + + } + + /// + /// 实体数据-使用内存流 + /// + /// + /// + /// + /// + /// + /// + public static async Task WriteExcelAsync(this IExcelHelper helper, Stream stream, DbDataReader datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + //ExcelHelper.WriteExcel>(stream, datas, sheetName, config); + using var trans = helper.BeginTransaction(stream, config); + await trans.WriteExcelAsync(datas, sheetName, cancellationToken: cancellationToken); + } + + /// + /// 实体数据-使用模板文件 + /// + /// + /// + /// + /// + /// + /// + public static async Task WriteExcelByTemplateAsync(this IExcelHelper helper, string path, string template, DbDataReader datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + var configuration = new ExcelConfiguration(); + config?.Invoke(configuration); + using var doc = ExcelDocument.CreateByTemplate(path, template, configuration); + await TransExcelHelper.WriteByTemplateAsync(doc, datas, sheetName, configuration, cancellationToken); + } + + +#endif + + #endregion +} diff --git a/src/LightExcel/Extensions/ExcelHeperExtensions.DataTable.cs b/src/LightExcel/Extensions/ExcelHeperExtensions.DataTable.cs new file mode 100644 index 0000000..36b9dcc --- /dev/null +++ b/src/LightExcel/Extensions/ExcelHeperExtensions.DataTable.cs @@ -0,0 +1,94 @@ +using LightExcel.Renders; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LightExcel; + +public static partial class ExcelHeperExtensions +{ + #region DataTableRender - DataTable作为数据源 + + /// + /// DataTable数据源-保存到文件 + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this IExcelHelper helper, string path, DataTable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcel(helper, path, datas, sheetName, config); + } + + /// + /// DataTable数据源-使用模板文件 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, string path, string template, DataTable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, path, template, datas, sheetName, config); + } + + /// + /// DataTable数据源-保存到内存流 + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this IExcelHelper helper, Stream stream, DataTable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcel(helper, stream, datas, sheetName, config); + } + + /// + /// DataTable数据源-使用模板流保存到内存流 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, Stream stream, Stream templateStream, DataTable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, stream, templateStream, datas, sheetName, config); + } + + /// + /// DataTable数据源-使用模板流保存到文件 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, string path, Stream templateStream, DataTable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, path, templateStream, datas, sheetName, config); + } + + /// + /// DataTable数据源-使用模板文件保存到内存流 + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, Stream stream, string template, DataTable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate(helper, stream, template, datas, sheetName, config); + } + + #endregion +} diff --git a/src/LightExcel/Extensions/ExcelHeperExtensions.Entity.cs b/src/LightExcel/Extensions/ExcelHeperExtensions.Entity.cs new file mode 100644 index 0000000..63862bc --- /dev/null +++ b/src/LightExcel/Extensions/ExcelHeperExtensions.Entity.cs @@ -0,0 +1,159 @@ +using LightExcel.OpenXml; +using LightExcel.Renders; +using LightExcel.Utils; +using System.Data; +using System.IO; + +namespace LightExcel; + +public static partial class ExcelHeperExtensions +{ + #region EnumerableEntityRender - 实体列表作为数据源 + + /// + /// 实体数据-保存到文件 + /// + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this IExcelHelper helper, string path, IEnumerable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcel>(helper, path, datas, sheetName, config); + } + + /// + /// 实体数据-使用内存流 + /// + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this IExcelHelper helper, Stream stream, IEnumerable datas, string sheetName = "Sheet1", Action? config = null) + { + //ExcelHelper.WriteExcel>(stream, datas, sheetName, config); + InternalWriteExcel>(helper, stream, datas, sheetName, config); + } + + /// + /// 实体数据-使用模板文件 + /// + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, string path, string template, IEnumerable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate>(helper, path, template, datas, sheetName, config); + } + + /// + /// 实体数据-写入和模板都使用内存流 + /// + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, Stream stream, Stream templateStream, IEnumerable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate>(helper, stream, templateStream, datas, sheetName, config); + } + + /// + /// 实体数据-模板使用流,保存到文件 + /// + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, string path, Stream templateStream, IEnumerable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate>(helper, path, templateStream, datas, sheetName, config); + } + + /// + /// 实体数据-使用模板文件,保存到内存流 + /// + /// + /// + /// + /// + /// + /// + /// + public static void WriteExcelByTemplate(this IExcelHelper helper, Stream stream, string template, IEnumerable datas, string sheetName = "Sheet1", Action? config = null) + { + InternalWriteExcelByTemplate>(helper, stream, template, datas, sheetName, config); + } + + #endregion + + #region 异步 + +#if NET6_0_OR_GREATER + + /// + /// 实体数据-保存到文件 + /// + /// + /// + /// + /// + /// + /// + public static async Task WriteExcelAsync(this IExcelHelper helper, string path, IAsyncEnumerable datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + using var trans = helper.BeginTransaction(path, config); + //var render = new AsyncEnumerableEntityRender(configuration); + await trans.WriteExcelAsync>(datas, sheetName, cancellationToken: cancellationToken); + + } + + /// + /// 实体数据-使用内存流 + /// + /// + /// + /// + /// + /// + /// + public static async Task WriteExcelAsync(this IExcelHelper helper, Stream stream, IAsyncEnumerable datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + //ExcelHelper.WriteExcel>(stream, datas, sheetName, config); + using var trans = helper.BeginTransaction(stream, config); + await trans.WriteExcelAsync>(datas, sheetName, cancellationToken: cancellationToken); + } + + /// + /// 实体数据-使用模板文件 + /// + /// + /// + /// + /// + /// + /// + public static async Task WriteExcelByTemplateAsync(this IExcelHelper helper, string path, string template, IAsyncEnumerable datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + var configuration = new ExcelConfiguration(); + config?.Invoke(configuration); + using var doc = ExcelDocument.CreateByTemplate(path, template, configuration); + await TransExcelHelper.WriteByTemplateAsync>(doc, datas, sheetName, configuration, cancellationToken); + } + + +#endif + + #endregion +} diff --git a/src/LightExcel/Extensions/ExcelHeperExtensions.cs b/src/LightExcel/Extensions/ExcelHeperExtensions.cs new file mode 100644 index 0000000..18a6575 --- /dev/null +++ b/src/LightExcel/Extensions/ExcelHeperExtensions.cs @@ -0,0 +1,101 @@ +using LightExcel.OpenXml; +using LightExcel.Renders; +using LightExcel.Utils; +using System.Data; +#pragma warning disable IDE0130 +namespace LightExcel; +public static partial class ExcelHeperExtensions +{ + // 文件路径 + internal static void InternalWriteExcel(IExcelHelper helper, string path, object datas, string sheetName = "Sheet1", Action? config = null) + where TRender : IDataRender + { + ExcelConfiguration configuration = new(); + config?.Invoke(configuration); + var render = RenderCreator.Create(configuration); + helper.WriteExcel(render, path, datas, sheetName, configuration); + } + // 文件流 + internal static void InternalWriteExcel(IExcelHelper helper, Stream stream, object datas, string sheetName = "Sheet1", Action? config = null) + where TRender : IDataRender + { + ExcelConfiguration configuration = new(); + config?.Invoke(configuration); + var render = RenderCreator.Create(configuration); + helper.WriteExcel(render, stream, datas, sheetName, configuration); + } + // 文件路径+模板文件 + internal static void InternalWriteExcelByTemplate(IExcelHelper helper, string path, string template, object datas, string sheetName = "Sheet1", Action? config = null) + where TRender : IDataRender + { + ExcelConfiguration configuration = new(); + config?.Invoke(configuration); + var render = RenderCreator.Create(configuration); + using var doc = ExcelDocument.CreateByTemplate(path, template, configuration); + helper.WriteExcelByTemplate(render, doc, datas, sheetName, configuration); + } + // 文件流+模板文件 + internal static void InternalWriteExcelByTemplate(IExcelHelper helper, Stream stream, string template, object datas, string sheetName = "Sheet1", Action? config = null) + where TRender : IDataRender + { + ExcelConfiguration configuration = new(); + config?.Invoke(configuration); + var render = RenderCreator.Create(configuration); + using var doc = ExcelDocument.CreateByTemplate(stream, template, configuration); + helper.WriteExcelByTemplate(render, doc, datas, sheetName, configuration); + } + // 文件路径+模板文件流 + internal static void InternalWriteExcelByTemplate(IExcelHelper helper, string path, Stream templateStream, object datas, string sheetName = "Sheet1", Action? config = null) + where TRender : IDataRender + { + ExcelConfiguration configuration = new(); + config?.Invoke(configuration); + //using var doc = ExcelDocument.CreateByTemplate(stream, template, configuration); + var render = RenderCreator.Create(configuration); + using var doc = ExcelDocument.CreateByTemplate(path, templateStream, configuration); + helper.WriteExcelByTemplate(render, doc, datas, sheetName, configuration); + } + // 文件流+模板文件流 + internal static void InternalWriteExcelByTemplate(IExcelHelper helper, Stream stream, Stream templateStream, object datas, string sheetName = "Sheet1", Action? config = null) + where TRender : IDataRender + { + ExcelConfiguration configuration = new(); + config?.Invoke(configuration); + //using var doc = ExcelDocument.CreateByTemplate(stream, template, configuration); + var render = RenderCreator.Create(configuration); + using var doc = ExcelDocument.CreateByTemplate(stream, templateStream, configuration); + helper.WriteExcelByTemplate(render, doc, datas, sheetName, configuration); + } + + + #region BeginTransaction + + public static ITransactionExcelHelper BeginTransaction(this IExcelHelper helper, string path, Action? config = null) + { + var configuration = new ExcelConfiguration(); + config?.Invoke(configuration); + if (File.Exists(path)) File.Delete(path); + var doc = ExcelDocument.Create(path, configuration); + return helper.BeginTransaction(doc, configuration); + } + + #endregion + + +#if NET6_0_OR_GREATER + + + // TODO 模板重载 + + + + //public static async Task WriteExcelByTemplateAsync(this IExcelHelper helper, string path, IAsyncEnumerable> data, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + //{ + // using var trans = helper.BeginTransaction(path, config); + // //var render = new AsyncEnumerableEntityRender(configuration); + // await trans.WriteExcelAsync, AsyncDictionaryRender>(data, sheetName, cancellationToken: cancellationToken); + //} + +#endif +} + diff --git a/src/LightExcel/Extensions/TransactionExcelHelperExtensions.cs b/src/LightExcel/Extensions/TransactionExcelHelperExtensions.cs new file mode 100644 index 0000000..d8ba12f --- /dev/null +++ b/src/LightExcel/Extensions/TransactionExcelHelperExtensions.cs @@ -0,0 +1,98 @@ +using LightExcel.Renders; +using LightExcel.Utils; +using System.Data; +#pragma warning disable IDE0130 +namespace LightExcel; + +public static partial class TransactionExcelHelperExtensions +{ + + private static void WriteExcel(ITransactionExcelHelper helper, object datas, string sheetName = "Sheet1", Action? config = null) + where TRender : IDataRender + { + var render = RenderCreator.Create(helper.Configuration); + var configuration = new TransConfiguration(helper.Configuration); + config?.Invoke(configuration); + helper.WriteExcel(render, datas, sheetName, configuration); + } + + #region EnumerableEntityRender - 实体列表作为数据源 + + /// + /// 实体数据-保存到文件 + /// + /// + /// + /// + /// + /// + public static void WriteExcel(this ITransactionExcelHelper helper, IEnumerable datas, string sheetName = "Sheet1", Action? config = null) + { + WriteExcel>(helper, datas, sheetName, config); + } + + #endregion + + #region DataTableRender - DataTable作为数据源 + + /// + /// DataTable数据源-保存到文件 + /// + /// + /// + /// + /// + public static void WriteExcel(this ITransactionExcelHelper helper, DataTable datas, string sheetName = "Sheet1", Action? config = null) + { + WriteExcel(helper, datas, sheetName, config); + } + + #endregion + + #region DictionaryRender - Dictionary作为数据源 + + /// + /// ]]>数据源 + /// + /// + /// + /// + /// + public static void WriteExcel(this ITransactionExcelHelper helper, IEnumerable> datas, string sheetName = "Sheet1", Action? config = null) + { + WriteExcel(helper, datas, sheetName, config); + } + + #endregion + + #region DataReaderRender - IDataReader作为数据源 + + /// + /// IDataReader数据源 + /// + /// + /// + /// + /// + public static void WriteExcel(this ITransactionExcelHelper helper, IDataReader datas, string sheetName = "Sheet1", Action? config = null) + { + WriteExcel(helper, datas, sheetName, config); + } + + #endregion + +#if NET6_0_OR_GREATER + + public static async Task WriteExcelAsync(this ITransactionExcelHelper helper, IAsyncEnumerable datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + await helper.WriteExcelAsync>(datas, sheetName, config, cancellationToken); + } + + public static async Task WriteExcelAsync(this ITransactionExcelHelper helper, IAsyncEnumerable> datas, string sheetName = "Sheet1", Action? config = null, CancellationToken cancellationToken = default) + { + await helper.WriteExcelAsync(datas, sheetName, config, cancellationToken); + } + +#endif +} + diff --git a/src/LightExcel/IDataRender.cs b/src/LightExcel/IDataRender.cs index 32609a2..6070df8 100644 --- a/src/LightExcel/IDataRender.cs +++ b/src/LightExcel/IDataRender.cs @@ -1,11 +1,34 @@ using LightExcel.OpenXml; +using LightExcel.OpenXml.Interfaces; namespace LightExcel { - internal interface IDataRender + public interface IRenderSheet + { + ExcelColumnInfo[] Columns { get; set; } + int MaxColumnIndex { get; set; } + int MaxRowIndex { get; set; } + void Write(IEnumerable children) where TNode : INode; + } + + public interface IDataRender + { + IEnumerable CollectExcelColumnInfo(object data); + internal void SetCustomHeaders(IEnumerable headers); + internal IEnumerable RenderHeader(ExcelColumnInfo[] columns, TransConfiguration configuration); + void Render(object data, IRenderSheet sheet, TransConfiguration configuration); + internal IEnumerable RenderBody(object data, IRenderSheet sheet, TransConfiguration configuration); + //void Render(object data, Sheet sheet, TransConfiguration configuration); + } + +#if NET6_0_OR_GREATER + internal interface IAsyncDataRender { IEnumerable CollectExcelColumnInfo(object data); - Row RenderHeader(ExcelColumnInfo[] columns, TransConfiguration configuration); - IEnumerable RenderBody(object data, Sheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration); + internal void SetCustomHeaders(IEnumerable headers); + IEnumerable RenderHeader(ExcelColumnInfo[] columns, TransConfiguration configuration); + IAsyncEnumerable RenderBodyAsync(object datas, Sheet sheet, TransConfiguration configuration, CancellationToken cancellationToken); + Task RenderAsync(object datas, Sheet sheet, TransConfiguration configuration, CancellationToken cancellationToken); } +#endif } diff --git a/src/LightExcel/IExcelHelper.cs b/src/LightExcel/IExcelHelper.cs index 27998dc..7a7f48c 100644 --- a/src/LightExcel/IExcelHelper.cs +++ b/src/LightExcel/IExcelHelper.cs @@ -1,4 +1,5 @@ -using System; +using LightExcel.OpenXml; +using System; using System.Collections.Generic; using System.Data; using System.Data.Common; @@ -10,25 +11,38 @@ namespace LightExcel { public interface ITransactionExcelHelper : IDisposable { - void WriteExcel(object data, string? sheetName = null, Action? config = null); + internal ExcelConfiguration Configuration { get; } + void WriteExcel(IDataRender render, object data, string? sheetName = null, TransConfiguration? config = null); +#if NET6_0_OR_GREATER + internal Task WriteExcelAsync(object datas, string? sheetName = null, Action? config = null, CancellationToken cancellationToken = default) + where TRender : IAsyncDataRender; + + //internal Task WriteByTemplateAsync(ExcelArchiveEntry doc, IAsyncEnumerable data, string sheetName, ExcelConfiguration configuration, CancellationToken cancellationToken = default) + // where TRender : IAsyncDataRender; +#endif } + public interface IExcelHelper { - // write file - void WriteExcel(string path, object data, string sheetName = "Sheet1", Action? config = null); - void WriteExcelByTemplate(string path, string template, object data, string sheetName = "Sheet1", Action? config = null); - ITransactionExcelHelper BeginTransaction(string path, Action? config = null); + #region 写入 + void WriteExcel(IDataRender render, string path, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null); + void WriteExcelByTemplate(IDataRender render, string path, string template, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null); + internal ITransactionExcelHelper BeginTransaction(ExcelArchiveEntry doc, ExcelConfiguration? config = null); // write stream - void WriteExcel(Stream stream, object data, string sheetName = "Sheet1", Action? config = null); - void WriteExcelByTemplate(Stream stream, Stream templateStream, object data, string sheetName = "Sheet1", Action? config = null); + void WriteExcel(IDataRender render, Stream stream, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null); + void WriteExcelByTemplate(IDataRender render, Stream stream, Stream templateStream, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null); + internal void WriteExcelByTemplate(IDataRender render, ExcelArchiveEntry doc, object data, string sheetName = "Sheet1", ExcelConfiguration? config = null); ITransactionExcelHelper BeginTransaction(Stream stream, Action? config = null); - // read + #endregion + + #region 读取 IExcelDataReader ReadExcel(string path, string? sheetName = null, Action? config = null); - IEnumerable QueryExcel(string path, string? sheetName , Action? config = null); - IEnumerable QueryExcel(string path, string? sheetName , Action? config = null); + IEnumerable QueryExcel(string path, string? sheetName, Action? config = null); + IEnumerable QueryExcel(string path, string? sheetName, Action? config = null); IExcelDataReader ReadExcel(Stream stream, string? sheetName = null, Action? config = null); IEnumerable QueryExcel(Stream stream, string? sheetName, Action? config = null); IEnumerable QueryExcel(Stream stream, string? sheetName, Action? config = null); + #endregion } } diff --git a/src/LightExcel/LightExcel.csproj b/src/LightExcel/LightExcel.csproj index e945a5c..1702f13 100644 --- a/src/LightExcel/LightExcel.csproj +++ b/src/LightExcel/LightExcel.csproj @@ -9,7 +9,7 @@ True MT.LightExcel 基于OpenXml的Excel读取与写入 - 2.1.3 + 2.1.4 https://github.com/MarvelTiter/LightExcel.git MIT Nuget.md diff --git a/src/LightExcel/OpenXml/Basic/SimpleNodeCollectionXmlPart.cs b/src/LightExcel/OpenXml/Basic/SimpleNodeCollectionXmlPart.cs index 84ab079..23111df 100644 --- a/src/LightExcel/OpenXml/Basic/SimpleNodeCollectionXmlPart.cs +++ b/src/LightExcel/OpenXml/Basic/SimpleNodeCollectionXmlPart.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using LightExcel.OpenXml.Interfaces; namespace LightExcel.OpenXml.Basic; diff --git a/src/LightExcel/OpenXml/Basic/XmlPart.cs b/src/LightExcel/OpenXml/Basic/XmlPart.cs index 309d485..9a4758a 100644 --- a/src/LightExcel/OpenXml/Basic/XmlPart.cs +++ b/src/LightExcel/OpenXml/Basic/XmlPart.cs @@ -28,11 +28,24 @@ public virtual void Write() } - public void Write(IEnumerable children) where TNode : INode + public void Write(IEnumerable children) + where TNode : INode { using var writer = archive!.GetWriter(Path); WriteImpl(writer, children); } +#if NET6_0_OR_GREATER + public async Task WriteAsync(IAsyncEnumerable children, CancellationToken cancellationToken = default) + where TNode : INode + { + using var writer = archive!.GetWriter(Path); + await WriteAsyncImpl(writer, children, cancellationToken); + } + protected virtual Task WriteAsyncImpl(LightExcelStreamWriter writer, IAsyncEnumerable children, CancellationToken cancellationToken = default) where TNode : INode + { + throw new NotSupportedException(); + } +#endif public void Replace(IEnumerable children) where TNode : INode { if (reader?.Path == Path) @@ -43,8 +56,21 @@ public void Replace(IEnumerable children) where TNode : INode } Write(children); } + + public void DeleteEntry() + { + if (reader?.Path == Path) + { + reader?.Dispose(); + reader = null; + archive!.GetEntry(Path)?.Delete(); + } + } + protected abstract void WriteImpl(LightExcelStreamWriter writer, IEnumerable children) where TNode : INode; + + protected virtual void Dispose(bool disposing) { if (!disposedValue) diff --git a/src/LightExcel/OpenXml/ExcelDocument.cs b/src/LightExcel/OpenXml/ExcelDocument.cs index c976371..f92d1b2 100644 --- a/src/LightExcel/OpenXml/ExcelDocument.cs +++ b/src/LightExcel/OpenXml/ExcelDocument.cs @@ -50,6 +50,18 @@ public static ExcelArchiveEntry CreateByTemplate(string path, string template, E return CreateByTemplate(fs, templateStream, configuration); } + public static ExcelArchiveEntry CreateByTemplate(Stream stream, string template, ExcelConfiguration configuration) + { + using var templateStream = File.Open(template, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); + return CreateByTemplate(stream, templateStream, configuration); + } + + public static ExcelArchiveEntry CreateByTemplate(string path, Stream templateStream, ExcelConfiguration configuration) + { + var fs = File.Create(path, 1024 * 512); + return CreateByTemplate(fs, templateStream, configuration); + } + public static ExcelArchiveEntry CreateByTemplate(Stream stream, Stream templateStream, ExcelConfiguration configuration) { templateStream.CopyTo(stream); diff --git a/src/LightExcel/OpenXml/Interfaces/INode.cs b/src/LightExcel/OpenXml/Interfaces/INode.cs index b31dd63..4b164c9 100644 --- a/src/LightExcel/OpenXml/Interfaces/INode.cs +++ b/src/LightExcel/OpenXml/Interfaces/INode.cs @@ -1,9 +1,9 @@ namespace LightExcel.OpenXml.Interfaces { - internal interface INode + public interface INode { //string ToXmlString(); - void WriteToXml(LightExcelStreamWriter writer); + internal void WriteToXml(LightExcelStreamWriter writer); } internal interface IStyleNode : INode { diff --git a/src/LightExcel/OpenXml/Interfaces/IXmlPart.cs b/src/LightExcel/OpenXml/Interfaces/IXmlPart.cs index 8b4f6db..357b88a 100644 --- a/src/LightExcel/OpenXml/Interfaces/IXmlPart.cs +++ b/src/LightExcel/OpenXml/Interfaces/IXmlPart.cs @@ -4,6 +4,9 @@ internal interface IXmlPart : IDisposable where T : INode { void Write(); void Write(IEnumerable children) where TNode : INode; + [Obsolete] void Replace(IEnumerable children) where TNode : INode; + + void DeleteEntry(); } } diff --git a/src/LightExcel/OpenXml/Sheet.cs b/src/LightExcel/OpenXml/Sheet.cs index 31c4872..e1133e1 100644 --- a/src/LightExcel/OpenXml/Sheet.cs +++ b/src/LightExcel/OpenXml/Sheet.cs @@ -10,7 +10,7 @@ namespace LightExcel.OpenXml /// /// xl/worksheets/sheet{id}.xml /// - internal class Sheet : NodeCollectionXmlPart, INode + internal class Sheet : NodeCollectionXmlPart, INode, IRenderSheet { public Sheet(ZipArchive archive, string id, string name, int sid) : base(archive, "") { @@ -55,22 +55,48 @@ public void WriteToXml(LightExcelStreamWriter writer) } protected override void WriteImpl(LightExcelStreamWriter writer, IEnumerable children) + { + WriteBefore(writer, out var dimensionWritePosition, out var colsWritePosition); + + foreach (var child in children) + { + child.WriteToXml(writer); + } + + WriteAfter(writer, dimensionWritePosition, colsWritePosition); + } +#if NET6_0_OR_GREATER + protected override async Task WriteAsyncImpl(LightExcelStreamWriter writer, IAsyncEnumerable children, CancellationToken cancellationToken = default) + { + WriteBefore(writer, out long dimensionWritePosition, out long colsWritePosition); + + await foreach (var child in children.WithCancellation(cancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + break; + } + child.WriteToXml(writer); + } + WriteAfter(writer, dimensionWritePosition, colsWritePosition); + } +#endif + private void WriteBefore(LightExcelStreamWriter writer, out long dimensionWritePosition, out long colsWritePosition) { writer.Write(""); writer.Write(""); // dimension - var dimensionWritePosition = writer.WriteAndFlush(""); + colsWritePosition = writer.WriteAndFlush(" />"); // var reserveLen = ReserveColsSpace(this); writer.Write(new string(' ', reserveLen)); writer.Write(""); - foreach (var child in children) - { - child.WriteToXml(writer); - } + } + private void WriteAfter(LightExcelStreamWriter writer, long dimensionWritePosition, long colsWritePosition) + { writer.Write(""); WriteMergeInfo(writer); writer.WriteAndFlush(""); @@ -85,6 +111,10 @@ protected override void WriteImpl(LightExcelStreamWriter writer, IEnumera } } + + + + private void WriteMergeInfo(LightExcelStreamWriter writer) { MergeCells?.WriteToXml(writer); diff --git a/src/LightExcel/OpenXml/SheetCollection.cs b/src/LightExcel/OpenXml/SheetCollection.cs index 9359157..215a151 100644 --- a/src/LightExcel/OpenXml/SheetCollection.cs +++ b/src/LightExcel/OpenXml/SheetCollection.cs @@ -19,6 +19,7 @@ public SheetCollection(ZipArchive archive, ExcelConfiguration configuration) protected override IEnumerable GetChildrenImpl() { + if (reader is null) yield break; if (!reader.IsStartWith("workbook", XmlHelper.MainNs)) yield break; if (!reader.ReadFirstContent()) yield break; while (!reader.EOF) diff --git a/src/LightExcel/OpenXml/StyleSheet.cs b/src/LightExcel/OpenXml/StyleSheet.cs index e689401..8fa5d23 100644 --- a/src/LightExcel/OpenXml/StyleSheet.cs +++ b/src/LightExcel/OpenXml/StyleSheet.cs @@ -24,8 +24,69 @@ protected override IEnumerable GetChildrenImpl() protected override void WriteImpl(LightExcelStreamWriter writer, IEnumerable children) { + /* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ writer.Write(""); writer.Write(""); + writer.Write(""); + writer.Write(""); + writer.Write(""); writer.Write(""); writer.Write(""); writer.Write(""); @@ -67,6 +128,9 @@ protected override void WriteImpl(LightExcelStreamWriter writer, IEnumera writer.Write(""); writer.Write(""); writer.Write(""); + writer.Write(""); + writer.Write(""); + writer.Write(""); writer.Write(""); writer.Write(""); writer.Write(""); diff --git a/src/LightExcel/RenderProvider.cs b/src/LightExcel/RenderProvider.cs index 2e01490..53e09d2 100644 --- a/src/LightExcel/RenderProvider.cs +++ b/src/LightExcel/RenderProvider.cs @@ -6,35 +6,36 @@ namespace LightExcel { public class RenderProvider { - internal static IDataRender GetDataRender(Type dataType, ExcelConfiguration configuration) - { - if (typeof(IDataReader).IsAssignableFrom(dataType)) - { - return new DataReaderRender(configuration); - } - else if (dataType == typeof(DataTable)) - { - return new DataTableRender(configuration); - } - else if (dataType.FindInterfaces((t, o) => t == typeof(IEnumerable), null).Length > 0) - { - var elementType = dataType.GetInterfaces().Where(t => IsGenericType(t)).SelectMany(t => t.GetGenericArguments()).FirstOrDefault(); - if (elementType != null) - { - if (elementType == typeof(Dictionary)) - { - return new DictionaryRender(configuration); - } - else - { - return new EnumerableEntityRender(elementType, configuration); - } - } - } - throw new NotImplementedException($"not supported data type: {dataType}"); + //internal static IDataRender GetDataRender(object data, ExcelConfiguration configuration) + //{ + // var dataType = data.GetType(); + // if (typeof(IDataReader).IsAssignableFrom(dataType)) + // { + // return new DataReaderRender(configuration); + // } + // else if (dataType == typeof(DataTable)) + // { + // return new DataTableRender(configuration); + // } + // else if (dataType.FindInterfaces((t, o) => t == typeof(IEnumerable), null).Length > 0) + // { + // var elementType = dataType.GetInterfaces().Where(t => IsGenericType(t)).SelectMany(t => t.GetGenericArguments()).FirstOrDefault(); + // if (elementType != null) + // { + // if (elementType == typeof(Dictionary)) + // { + // return new DictionaryRender(configuration); + // } + // else + // { + // return new EnumerableEntityRender(elementType, configuration); + // } + // } + // } + // throw new NotImplementedException($"not supported data type: {dataType}"); - bool IsGenericType(Type type1) => type1.IsGenericType && type1.GetGenericTypeDefinition() == typeof(IEnumerable<>); + // bool IsGenericType(Type type1) => type1.IsGenericType && type1.GetGenericTypeDefinition() == typeof(IEnumerable<>); - } + //} } } diff --git a/src/LightExcel/Renders/AsyncDataReaderRender.cs b/src/LightExcel/Renders/AsyncDataReaderRender.cs new file mode 100644 index 0000000..f3b9a9f --- /dev/null +++ b/src/LightExcel/Renders/AsyncDataReaderRender.cs @@ -0,0 +1,81 @@ +using LightExcel.OpenXml; +using LightExcel.Utils; +using System.Data; +using System.Data.Common; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace LightExcel.Renders; +#if NET6_0_OR_GREATER +internal class AsyncDataReaderRender(ExcelConfiguration configuration) : AsyncRenderBase(configuration) +{ + public override IEnumerable CollectExcelColumnInfo(DbDataReader data) + { + for (int i = 0; i < data.FieldCount; i++) + { + var name = data.GetName(i); + var col = new ExcelColumnInfo(name); + col.NumberFormat = Configuration.CheckCellNumberFormat(name); + col.Type = data.GetFieldType(i); + col.ColumnIndex = i + 1; + AssignDynamicInfo(col); + yield return col; + } + } + + public override async Task RenderAsync(DbDataReader datas, Sheet sheet, TransConfiguration configuration, CancellationToken cancellationToken) + { + if (sheet.Columns.Length == 0) + { + ExcelColumnInfo[] columns = [.. CollectExcelColumnInfo(datas)]; + sheet.Columns = columns; + } + + var allRows = CollectALlRows(datas, sheet, sheet.Columns, configuration, cancellationToken); + await sheet.WriteAsync(allRows, cancellationToken); + } + + public override async IAsyncEnumerable RenderBodyAsync(DbDataReader datas, Sheet sheet, TransConfiguration configuration, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var reader = datas; + //var reader = data as IDataReader ?? throw new ArgumentException(); + var rowIndex = Configuration.StartRowIndex; + var maxColumnIndex = 0; + while (await reader.ReadAsync(cancellationToken)) + { + var row = new Row() { RowIndex = ++rowIndex }; + var cellIndex = 0; + foreach (var col in sheet.Columns) + { + if (col.Ignore) continue; + var value = reader.GetValue(col.ColumnIndex - 1); + cellIndex = col.ColumnIndex; + var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, configuration); + row.AppendChild(cell); + } + maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); + yield return row; + } + sheet.MaxColumnIndex = maxColumnIndex; + sheet.MaxRowIndex = rowIndex; + } + + private async IAsyncEnumerable CollectALlRows(DbDataReader reader, Sheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration, [EnumeratorCancellation] CancellationToken cancellationToken) + { + if (Configuration.UseHeader) + { + var headers = RenderHeader(columns, configuration); + foreach (var row in headers) + { + yield return row; + } + } + var rows = RenderBodyAsync(reader, sheet, configuration, cancellationToken); + await foreach (var row in rows.WithCancellation(cancellationToken)) + { + yield return row; + } + } +} +#endif \ No newline at end of file diff --git a/src/LightExcel/Renders/AsyncDictionaryRender.cs b/src/LightExcel/Renders/AsyncDictionaryRender.cs new file mode 100644 index 0000000..b3171cf --- /dev/null +++ b/src/LightExcel/Renders/AsyncDictionaryRender.cs @@ -0,0 +1,50 @@ + +using LightExcel.OpenXml; +using LightExcel.Utils; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace LightExcel.Renders; +#if NET6_0_OR_GREATER +internal class AsyncDictionaryRender(ExcelConfiguration configuration) : AsyncEnumerableEntityRender>(configuration) +{ + public override IEnumerable CollectExcelColumnInfo(Dictionary data) + { + int index = 1; + foreach (var item in data.Keys) + { + var col = new ExcelColumnInfo(item) + { + NumberFormat = Configuration.CheckCellNumberFormat(item), + ColumnIndex = index++ + }; + AssignDynamicInfo(col); + yield return col; + } + } + + public override async IAsyncEnumerable RenderBodyAsync(IAsyncEnumerable> data, Sheet sheet, TransConfiguration configuration, [EnumeratorCancellation]CancellationToken cancellationToken) + { + var rowIndex = Configuration.StartRowIndex; + var maxColumnIndex = 0; + await foreach (var item in data.WithCancellation(cancellationToken)) + { + if (item is null) continue; + var row = new Row() { RowIndex = ++rowIndex }; + var cellIndex = 0; + foreach (var col in sheet.Columns) + { + if (col.Ignore) continue; + item.TryGetValue(col.Name, out var value); + cellIndex = col.ColumnIndex; + var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, configuration); + row.AppendChild(cell); + } + maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); + yield return row; + } + sheet.MaxColumnIndex = maxColumnIndex; + sheet.MaxRowIndex = rowIndex; + } +} +#endif diff --git a/src/LightExcel/Renders/AsyncEnumerableEntityRender.cs b/src/LightExcel/Renders/AsyncEnumerableEntityRender.cs new file mode 100644 index 0000000..58f02bc --- /dev/null +++ b/src/LightExcel/Renders/AsyncEnumerableEntityRender.cs @@ -0,0 +1,74 @@ +using LightExcel.Attributes; +using LightExcel.OpenXml; +using LightExcel.Utils; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +#if NET6_0_OR_GREATER +namespace LightExcel.Renders +{ + internal class AsyncEnumerableEntityRender(ExcelConfiguration configuration) : AsyncEnumerableRenderBase(configuration) + { + private readonly Type elementType = typeof(T); + + public override IEnumerable CollectExcelColumnInfo(T data) + { + var properties = elementType.GetProperties(); + int index = 1; + foreach (var prop in properties) + { + ExcelColumnAttribute? excelColumnAttribute = prop.GetCustomAttribute(); + if (excelColumnAttribute?.Ignore ?? false) continue; +#if NET6_0_OR_GREATER + var displayAttribute = prop.GetCustomAttribute(); + var col = new ExcelColumnInfo(excelColumnAttribute?.Name ?? displayAttribute?.Name ?? prop.Name); +#else + var col = new ExcelColumnInfo(excelColumnAttribute?.Name ?? prop.Name); +#endif + + col.Ignore = excelColumnAttribute?.Ignore ?? false; + col.Property = new Property(prop); + col.Type = prop.PropertyType; + col.NumberFormat = excelColumnAttribute?.NumberFormat ?? false; + col.Format = excelColumnAttribute?.Format; + col.ColumnIndex = index++; + col.AutoWidth = excelColumnAttribute?.AutoWidth ?? false; + col.Width = excelColumnAttribute?.Width; + AssignDynamicInfo(col); + yield return col; + } + } + + + public override async IAsyncEnumerable RenderBodyAsync(IAsyncEnumerable data, Sheet sheet, TransConfiguration configuration, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var rowIndex = Configuration.StartRowIndex; + var maxColumnIndex = 0; + await foreach (var item in data.WithCancellation(cancellationToken)) + { + if (item is null) continue; + var row = new Row() { RowIndex = ++rowIndex }; + var cellIndex = 0; + foreach (var col in sheet.Columns) + { + if (col.Property == null) + { + var p = elementType.GetProperty(col.Name); + if (p == null) continue; + col.Property = new Property(p); + } + var value = col.Property.GetValue(item); + cellIndex = col.ColumnIndex; + var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, configuration); + row.AppendChild(cell); + } + maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); + yield return row; + } + sheet.MaxColumnIndex = maxColumnIndex; + sheet.MaxRowIndex = rowIndex; + } + } +} +#endif \ No newline at end of file diff --git a/src/LightExcel/Renders/AsyncRenderBase.cs b/src/LightExcel/Renders/AsyncRenderBase.cs new file mode 100644 index 0000000..6ae148e --- /dev/null +++ b/src/LightExcel/Renders/AsyncRenderBase.cs @@ -0,0 +1,79 @@ +using LightExcel.OpenXml; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace LightExcel.Renders; +#if NET6_0_OR_GREATER +internal abstract class AsyncRenderBase(ExcelConfiguration configuration) : RenderBase(configuration), IAsyncDataRender +{ + + public virtual IEnumerable CollectExcelColumnInfo(object data) + => CollectExcelColumnInfo((TElement)data); + public virtual IAsyncEnumerable RenderBodyAsync(object datas, Sheet sheet, TransConfiguration configuration, CancellationToken cancellationToken) + => RenderBodyAsync((TData)datas, sheet, configuration, cancellationToken); + public virtual Task RenderAsync(object datas, Sheet sheet, TransConfiguration configuration, CancellationToken cancellationToken) + => RenderAsync((TData)datas, sheet, configuration, cancellationToken); + + public abstract IEnumerable CollectExcelColumnInfo(TElement data); + + public abstract IAsyncEnumerable RenderBodyAsync(TData datas, Sheet sheet, TransConfiguration configuration, CancellationToken cancellationToken); + + public abstract Task RenderAsync(TData datas, Sheet sheet, TransConfiguration configuration, CancellationToken cancellationToken); +} + +/// +/// 使用作为数据源 +/// +/// +/// +internal abstract class AsyncEnumerableRenderBase(ExcelConfiguration configuration) + : AsyncRenderBase, TElement>(configuration) +{ + public override async Task RenderAsync(IAsyncEnumerable datas, Sheet sheet, TransConfiguration configuration, CancellationToken cancellationToken) + { + // 获取第一个元素(但不影响后续遍历) + await using var enumerator = datas.GetAsyncEnumerator(cancellationToken); + if (!await enumerator.MoveNextAsync()) + { + return; // 无数据 + } + var firstItem = enumerator.Current; + if (sheet.Columns.Length == 0) + { + ExcelColumnInfo[] columns = [.. CollectExcelColumnInfo(firstItem)]; + sheet.Columns = columns; + } + + var allRows = CollectALlRows(GetRemainingData(enumerator), sheet, sheet.Columns, configuration, cancellationToken); + await sheet.WriteAsync(allRows, cancellationToken); + } + + private async IAsyncEnumerable CollectALlRows(IAsyncEnumerable datas, Sheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration, [EnumeratorCancellation] CancellationToken cancellationToken) + { + if (Configuration.UseHeader) + { + var headers = RenderHeader(columns, configuration); + foreach (var row in headers) + { + yield return row; + } + } + var rows = RenderBodyAsync(datas, sheet, configuration, cancellationToken); + await foreach (var row in rows.WithCancellation(cancellationToken)) + { + yield return row; + } + } + + private static async IAsyncEnumerable GetRemainingData(IAsyncEnumerator enumerator) + { + // 已经 MoveNext 一次,所以直接 yield 剩余数据 + do + { + yield return enumerator.Current; + } while (await enumerator.MoveNextAsync()); + } +} + +#endif diff --git a/src/LightExcel/Renders/DataReaderRender.cs b/src/LightExcel/Renders/DataReaderRender.cs index 12cfb77..056ccbd 100644 --- a/src/LightExcel/Renders/DataReaderRender.cs +++ b/src/LightExcel/Renders/DataReaderRender.cs @@ -4,42 +4,43 @@ namespace LightExcel.Renders { - internal class DataReaderRender : RenderBase + internal class DataReaderRender(ExcelConfiguration configuration) : SyncRenderBase(configuration) { - public DataReaderRender(ExcelConfiguration configuration) : base(configuration) { } - public override IEnumerable CollectExcelColumnInfo(object data) + public override IEnumerable CollectExcelColumnInfo(IDataReader data) { - if (data is IDataReader d) + //if (data is not IDataReader d) + //{ + // yield break; + //} + for (int i = 0; i < data.FieldCount; i++) { - for (int i = 0; i < d.FieldCount; i++) - { - var name = d.GetName(i); - var col = new ExcelColumnInfo(name); - col.NumberFormat = Configuration.CheckCellNumberFormat(name); - col.Type = d.GetFieldType(i); - col.ColumnIndex = i + 1; - AssignDynamicInfo(col); - yield return col; - } + var name = data.GetName(i); + var col = new ExcelColumnInfo(name); + col.NumberFormat = Configuration.CheckCellNumberFormat(name); + col.Type = data.GetFieldType(i); + col.ColumnIndex = i + 1; + AssignDynamicInfo(col); + yield return col; } } + public override IDataReader GetFirstElement(IDataReader data) => data; - public override IEnumerable RenderBody(object data, Sheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration) + public override IEnumerable RenderBody(IDataReader data, IRenderSheet sheet, TransConfiguration configuration) { - var reader = data as IDataReader ?? throw new ArgumentException(); + //var reader = data as IDataReader ?? throw new ArgumentException(); + var reader = data; var rowIndex = Configuration.StartRowIndex; var maxColumnIndex = 0; while (reader.Read()) { var row = new Row() { RowIndex = ++rowIndex }; var cellIndex = 0; - foreach (var col in columns) + foreach (var col in sheet.Columns) { if (col.Ignore) continue; var value = reader.GetValue(col.ColumnIndex - 1); cellIndex = col.ColumnIndex; - var nf = configuration.NumberFormatColumnFilter(col); - var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, nf, Configuration); + var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, configuration); //var cell = new Cell(); //var (v, t) = CellHelper.FormatCell(value, Configuration, col); ////cell.Type = CellHelper.ConvertCellType(col.Type); diff --git a/src/LightExcel/Renders/DataTableRender.cs b/src/LightExcel/Renders/DataTableRender.cs index 549208a..1ba0fed 100644 --- a/src/LightExcel/Renders/DataTableRender.cs +++ b/src/LightExcel/Renders/DataTableRender.cs @@ -4,43 +4,46 @@ namespace LightExcel.Renders { - internal class DataTableRender : RenderBase + internal class DataTableRender(ExcelConfiguration configuration) : SyncRenderBase(configuration) { - public DataTableRender(ExcelConfiguration configuration) : base(configuration) { } - public override IEnumerable CollectExcelColumnInfo(object data) + public override IEnumerable CollectExcelColumnInfo(DataRow data) { - if (data is DataTable dt) + //if (data is not DataTable dt) + //{ + // yield break; + //} + var dt = data.Table; + int index = 1; + foreach (DataColumn column in dt.Columns) { - int index = 1; - foreach (DataColumn column in dt.Columns) - { - var col = new ExcelColumnInfo(column.ColumnName); - col.NumberFormat = Configuration.CheckCellNumberFormat(column.ColumnName, dt.TableName); - col.Type = column.DataType; - col.ColumnIndex = index++; - AssignDynamicInfo(col); - yield return col; - } + var col = new ExcelColumnInfo(column.ColumnName); + col.NumberFormat = Configuration.CheckCellNumberFormat(column.ColumnName, dt.TableName); + col.Type = column.DataType; + col.ColumnIndex = index++; + AssignDynamicInfo(col); + yield return col; } } - public override IEnumerable RenderBody(object data, Sheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration) + public override DataRow GetFirstElement(DataTable data) => data.NewRow(); + + public override IEnumerable RenderBody(DataTable data, IRenderSheet sheet, TransConfiguration configuration) { - var values = data as DataTable; + //var values = data as DataTable; + var values = data.Rows; var rowIndex = Configuration.StartRowIndex; var maxColumnIndex = 0; - foreach (DataRow item in values!.Rows) + foreach (DataRow item in values) { if (item is null) continue; var row = new Row() { RowIndex = ++rowIndex }; var cellIndex = 0; - foreach (var col in columns) + foreach (var col in sheet.Columns) { if (col.Ignore) continue; var value = item[col.Name]; cellIndex = col.ColumnIndex; - var nf = configuration.NumberFormatColumnFilter(col); - var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, nf, Configuration); + var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, configuration); row.AppendChild(cell); } maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); diff --git a/src/LightExcel/Renders/DictionaryRender.cs b/src/LightExcel/Renders/DictionaryRender.cs index fab98aa..282674a 100644 --- a/src/LightExcel/Renders/DictionaryRender.cs +++ b/src/LightExcel/Renders/DictionaryRender.cs @@ -4,45 +4,47 @@ namespace LightExcel.Renders { - internal class DictionaryRender : RenderBase + internal class DictionaryRender(ExcelConfiguration configuration) : SyncRenderBase>, Dictionary>(configuration) { - public DictionaryRender(ExcelConfiguration configuration) : base(configuration) { } - - public override IEnumerable CollectExcelColumnInfo(object data) + public override IEnumerable CollectExcelColumnInfo(Dictionary data) { - if (data is IEnumerable> d) + //if (data is not IEnumerable> d) + //{ + // yield break; + //} + int index = 1; + foreach (var item in data.Keys) { - int index = 1; - foreach (var item in d.First().Keys) + var col = new ExcelColumnInfo(item) { - var col = new ExcelColumnInfo(item) - { - NumberFormat = Configuration.CheckCellNumberFormat(item), - ColumnIndex = index++ - }; - AssignDynamicInfo(col); - yield return col; - } + NumberFormat = Configuration.CheckCellNumberFormat(item), + ColumnIndex = index++ + }; + AssignDynamicInfo(col); + yield return col; } } - public override IEnumerable RenderBody(object data, Sheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration) + public override Dictionary GetFirstElement(IEnumerable> data) + => data.First(); + + public override IEnumerable RenderBody(IEnumerable> data, IRenderSheet sheet, TransConfiguration configuration) { - var values = data as IEnumerable> ?? throw new ArgumentException(); + //var values = data as IEnumerable> ?? throw new ArgumentException(); + var values = data; var rowIndex = Configuration.StartRowIndex; var maxColumnIndex = 0; - foreach (var item in values!) + foreach (var item in values) { if (item is null) continue; var row = new Row() { RowIndex = ++rowIndex }; var cellIndex = 0; - foreach (var col in columns) + foreach (var col in sheet.Columns) { if (col.Ignore) continue; item.TryGetValue(col.Name, out var value); cellIndex = col.ColumnIndex; - var nf = configuration.NumberFormatColumnFilter(col); - var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, nf, Configuration); + var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, configuration); row.AppendChild(cell); } maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); diff --git a/src/LightExcel/Renders/EnumerableEntityRender.cs b/src/LightExcel/Renders/EnumerableEntityRender.cs index dbaee78..15c6fff 100644 --- a/src/LightExcel/Renders/EnumerableEntityRender.cs +++ b/src/LightExcel/Renders/EnumerableEntityRender.cs @@ -6,16 +6,16 @@ namespace LightExcel.Renders { - internal class EnumerableEntityRender : RenderBase//, IDataRender + internal class EnumerableEntityRender : SyncRenderBase, T>//, IDataRender { private readonly Type elementType; - public EnumerableEntityRender(Type elementType, ExcelConfiguration configuration) : base(configuration) + public EnumerableEntityRender(ExcelConfiguration configuration) : base(configuration) { - this.elementType = elementType; + this.elementType = typeof(T); } - public override IEnumerable CollectExcelColumnInfo(object data) + public override IEnumerable CollectExcelColumnInfo(T data) { var properties = elementType.GetProperties(); int index = 1; @@ -38,21 +38,25 @@ public override IEnumerable CollectExcelColumnInfo(object data) col.ColumnIndex = index++; col.AutoWidth = excelColumnAttribute?.AutoWidth ?? false; col.Width = excelColumnAttribute?.Width; + AssignDynamicInfo(col); yield return col; } } - public override IEnumerable RenderBody(object data, Sheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration) + public override T GetFirstElement(IEnumerable data) => data.First(); + + public override IEnumerable RenderBody(IEnumerable data, IRenderSheet sheet, TransConfiguration configuration) { - var values = data as IEnumerable ?? throw new ArgumentException(); + //var values = data as IEnumerable ?? throw new ArgumentException(); + var values = data; var rowIndex = Configuration.StartRowIndex; var maxColumnIndex = 0; - foreach (var item in values!) + foreach (var item in values) { if (item is null) continue; var row = new Row() { RowIndex = ++rowIndex }; var cellIndex = 0; - foreach (var col in columns) + foreach (var col in sheet.Columns) { if (col.Property == null) { @@ -70,8 +74,7 @@ public override IEnumerable RenderBody(object data, Sheet sheet, ExcelColum //cell.Value = v; //cell.Type = t; //cell.StyleIndex = col.NumberFormat || configuration.NumberFormatColumnFilter(col) ? "1" : null; - var nf = configuration.NumberFormatColumnFilter(col); - var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, nf, Configuration); + var cell = CellHelper.CreateCell(cellIndex, rowIndex, value, col, configuration); row.AppendChild(cell); } maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); diff --git a/src/LightExcel/Renders/RenderBase.cs b/src/LightExcel/Renders/RenderBase.cs index d26cbd4..bb353df 100644 --- a/src/LightExcel/Renders/RenderBase.cs +++ b/src/LightExcel/Renders/RenderBase.cs @@ -1,45 +1,47 @@ using LightExcel.OpenXml; using LightExcel.Utils; -namespace LightExcel.Renders +namespace LightExcel.Renders; +internal abstract class RenderBase(ExcelConfiguration configuration) { - internal abstract class RenderBase : IDataRender + public ExcelConfiguration Configuration { get; } = configuration; + IEnumerable? customHeaders; + public virtual void SetCustomHeaders(IEnumerable headers) { - public ExcelConfiguration Configuration { get; } - public RenderBase(ExcelConfiguration configuration) - { - Configuration = configuration; - } - - - public abstract IEnumerable CollectExcelColumnInfo(object data); - - public abstract IEnumerable RenderBody(object data, Sheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration); + customHeaders = headers; + } - public virtual Row RenderHeader(ExcelColumnInfo[] columns, TransConfiguration configuration) + public virtual IEnumerable RenderHeader(ExcelColumnInfo[] columns, TransConfiguration configuration) + { + if (customHeaders is not null) { - var row = new Row() { RowIndex = 1 }; - Configuration.StartRowIndex = 1; - var index = 0; - foreach (var col in columns) + foreach (var item in customHeaders) { - var cell = new Cell - { - Reference = ReferenceHelper.ConvertXyToCellReference(++index, 1), - Type = "str", - Value = col.Name - }; - row.AppendChild(cell); + yield return item; } - return row; + yield break; } - - protected void AssignDynamicInfo(ExcelColumnInfo origin) + var row = new Row() { RowIndex = 1 }; + Configuration.StartRowIndex = 1; + var index = 0; + foreach (var col in columns) { - var dyCol = Configuration[origin.Name]; - origin.Format = dyCol?.Format; - origin.Width = dyCol?.Width; - origin.AutoWidth = !origin.Width.HasValue && (dyCol?.AutoWidth ?? Configuration.AutoWidth); + var cell = new Cell + { + Reference = ReferenceHelper.ConvertXyToCellReference(++index, 1), + Type = "str", + Value = col.Name + }; + row.AppendChild(cell); } + yield return row; + } + + protected void AssignDynamicInfo(ExcelColumnInfo origin) + { + var dyCol = Configuration[origin.Name]; + origin.Format ??= dyCol?.Format; + origin.Width ??= dyCol?.Width; + origin.AutoWidth = !origin.Width.HasValue && (dyCol?.AutoWidth ?? Configuration.AutoWidth); } } \ No newline at end of file diff --git a/src/LightExcel/Renders/SyncRenderBase.cs b/src/LightExcel/Renders/SyncRenderBase.cs new file mode 100644 index 0000000..dc36fbc --- /dev/null +++ b/src/LightExcel/Renders/SyncRenderBase.cs @@ -0,0 +1,48 @@ +using LightExcel.OpenXml; +using System.Reflection; + +namespace LightExcel.Renders +{ + internal abstract class SyncRenderBase(ExcelConfiguration configuration) + : RenderBase(configuration), IDataRender + { + public IEnumerable CollectExcelColumnInfo(object data) + => CollectExcelColumnInfo(GetFirstElement((T)data)); + + + public IEnumerable RenderBody(object data, IRenderSheet sheet, TransConfiguration configuration) + => RenderBody((T)data, sheet, configuration); + + public abstract IEnumerable CollectExcelColumnInfo(TElement data); + public abstract IEnumerable RenderBody(T data, IRenderSheet sheet, TransConfiguration configuration); + public abstract TElement GetFirstElement(T data); + + public void Render(object data, IRenderSheet sheet, TransConfiguration configuration) + { + if (sheet.Columns.Length == 0) + { + ExcelColumnInfo[] columns = [.. CollectExcelColumnInfo(data)]; + sheet.Columns = columns; + } + var allRows = CollectALlRows(data, sheet, sheet.Columns, configuration); + sheet.Write(allRows); + } + + private IEnumerable CollectALlRows(object data, IRenderSheet sheet, ExcelColumnInfo[] columns, TransConfiguration configuration) + { + if (Configuration.UseHeader) + { + var headers = RenderHeader(columns, configuration); + foreach (var row in headers) + { + yield return row; + } + } + var datas = RenderBody(data, sheet, configuration); + foreach (var row in datas) + { + yield return row; + } + } + } +} \ No newline at end of file diff --git a/src/LightExcel/TransExcelHelper.cs b/src/LightExcel/TransExcelHelper.cs index 07771d6..f06b641 100644 --- a/src/LightExcel/TransExcelHelper.cs +++ b/src/LightExcel/TransExcelHelper.cs @@ -1,19 +1,22 @@ using LightExcel.OpenXml; +using LightExcel.Renders; +using LightExcel.Utils; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace LightExcel { - internal class TransExcelHelper : ITransactionExcelHelper + internal partial class TransExcelHelper : ITransactionExcelHelper { private bool disposedValue; private ExcelArchiveEntry? excelArchive; - private readonly ExcelConfiguration configuration; + public ExcelConfiguration Configuration { get; set; } public TransExcelHelper(string path, ExcelConfiguration configuration) { @@ -26,41 +29,101 @@ public TransExcelHelper(string path, ExcelConfiguration configuration) excelArchive = ExcelDocument.Create(path, configuration); } - this.configuration = configuration; + Configuration = configuration; } public TransExcelHelper(Stream stream, ExcelConfiguration configuration) { excelArchive = ExcelDocument.Create(stream, configuration); - this.configuration = configuration; + Configuration = configuration; } - public void WriteExcel(object data, string? sheetName = null, Action? config = null) + public TransExcelHelper(ExcelArchiveEntry doc, ExcelConfiguration configuration) { - var cfg = new TransConfiguration(); - config?.Invoke(cfg); - var sheet = excelArchive!.WorkBook.AddNewSheet(sheetName); - var render = RenderProvider.GetDataRender(data.GetType(), configuration); - var columns = render.CollectExcelColumnInfo(data).ToArray(); - sheet.Columns = columns; - var all = NeedToReaderRows(render, sheet, data, columns, cfg); - sheet!.Write(all); + excelArchive = doc; + Configuration = configuration; + } + + public void WriteExcel(IDataRender render, object data, string? sheetName = null, TransConfiguration? config = null) + { + //WriteExcel(render, data, sheetName, config); + config ??= new TransConfiguration(Configuration); + Sheet sheet = excelArchive!.WorkBook.AddNewSheet(sheetName); + render.Render(data, sheet, config); } - private IEnumerable NeedToReaderRows(IDataRender render, Sheet sheet, object data, ExcelColumnInfo[] columns, TransConfiguration transCfg) + internal static (List, Sheet) HandleTemplateHeader(ExcelArchiveEntry doc, string sheetName, ExcelConfiguration configuration) { - if (configuration.UseHeader) + configuration.FillByTemplate = true; + // 获取sheet对象 + var sheet = doc.WorkBook.WorkSheets.FirstOrDefault() ?? throw new Exception("read excel sheet failed"); + // 获取最后一行当模板 + var header = sheet.ToList(); + var templateRow = header.Last(); + // 获取共享字符串列表 + var sst = doc.WorkBook.SharedStrings?.ToList(); + //var render = RenderProvider.GetDataRender(data.GetType(), configuration); + if (configuration.FillWithPlacholder) + { + sheet.Columns = [.. CollectExcelColumnInfos(templateRow, sst)]; + } + if (configuration.FillWithPlacholder) { - var header = render.RenderHeader(columns, transCfg); - yield return header; + templateRow.IsTemplateRow = true; + configuration.StartRowIndex = templateRow.RowIndex - 1; } - var datas = render.RenderBody(data, sheet, columns, transCfg); - foreach (var row in datas) + else { - yield return row; + configuration.StartRowIndex = templateRow.RowIndex; } + return (header, sheet); } + /// + /// 仅支持第一个sheet + /// + /// + /// + /// + /// + /// + internal static void WriteByTemplate(IDataRender render, ExcelArchiveEntry doc, object data, string sheetName, ExcelConfiguration configuration) + { + var (header, sheet) = HandleTemplateHeader(doc, sheetName, configuration); + //var newRows = render.RenderBody(data, sheet, new TransConfiguration(configuration) { SheetNumberFormat = configuration.AddSheetNumberFormat }); + //sheet.Replace(header.Concat(newRows)); + render.SetCustomHeaders(header); + sheet.DeleteEntry(); + render.Render(data, sheet, new TransConfiguration(configuration) { SheetNumberFormat = configuration.AddSheetNumberFormat }); + doc.Save(); + } + +#if NET6_0_OR_GREATER + + public async Task WriteExcelAsync(object data, string? sheetName = null, Action? config = null, CancellationToken cancellationToken = default) + where TRender : IAsyncDataRender + { + var render = AsyncRenderCreator.Create(Configuration); + var cfg = new TransConfiguration(Configuration); + config?.Invoke(cfg); + var sheet = excelArchive!.WorkBook.AddNewSheet(sheetName); + await render.RenderAsync(data, sheet, cfg, cancellationToken); + } + + internal static async Task WriteByTemplateAsync(ExcelArchiveEntry doc, object data, string sheetName, ExcelConfiguration configuration, CancellationToken cancellationToken = default) + where TRender : IAsyncDataRender + { + var render = AsyncRenderCreator.Create(configuration); + var (header, sheet) = HandleTemplateHeader(doc, sheetName, configuration); + render.SetCustomHeaders(header); + sheet.DeleteEntry(); + await render.RenderAsync(data, sheet, new TransConfiguration(configuration) { SheetNumberFormat = configuration.AddSheetNumberFormat }, cancellationToken); + doc.Save(); + } + +#endif + + protected virtual void Dispose(bool disposing) { if (!disposedValue) @@ -80,6 +143,37 @@ public void Dispose() GC.SuppressFinalize(this); } - +#if NET8_0_OR_GREATER + [GeneratedRegex("{{(.+)}}")] + private static partial Regex ExtractColumn(); +#else + static readonly Regex extract = new("{{(.+)}}"); + private static Regex ExtractColumn() => extract; +#endif + private static IEnumerable CollectExcelColumnInfos(Row templateRow, List? sst) + { + foreach (var cell in templateRow.Children) + { + string? name = cell.Value; + var (X, Y) = ReferenceHelper.ConvertCellReferenceToXY(cell.Reference); + if (cell.Type == "s") + { + if (int.TryParse(name, out var s) && sst!.Count > s) + { + name = sst[s].Content; + } + } + if (name != null) + { + var match = ExtractColumn().Match(name); + if (match.Success) + { + name = match.Groups[1].Value; + var col = new ExcelColumnInfo(name) { ColumnIndex = X ?? 0, StyleIndex = cell.StyleIndex }; + yield return col; + } + } + } + } } } diff --git a/src/LightExcel/Utils/CellHelper.cs b/src/LightExcel/Utils/CellHelper.cs index a32f5b3..6bc0c53 100644 --- a/src/LightExcel/Utils/CellHelper.cs +++ b/src/LightExcel/Utils/CellHelper.cs @@ -33,26 +33,26 @@ internal static string ConvertCellType(Type? type, bool shared = false) } internal static Cell EmptyCell(string cr) => new() { Reference = cr, Type = "str" }; - internal static Cell CreateCell(int x, int y, object? value, ExcelColumnInfo col, bool filted, ExcelConfiguration config) + internal static Cell CreateCell(int x, int y, object? value, ExcelColumnInfo col, TransConfiguration transConfig) { var cell = new Cell(); cell.Reference = ReferenceHelper.ConvertXyToCellReference(x, y); //cell.Type = config.GetValueTypeAtRuntime && (value?.IsStringNumber() ?? false) ? "n" : ConvertCellType(col.Type ?? value?.GetType()); //cell.Value = GetCellValue(col, value, config); - var (v, t) = FormatCell(value, config, col); - cell.Value = v; - cell.Type = t; - cell.StyleIndex = col.StyleIndex ?? (col.NumberFormat || filted ? "1" : null); - CalcCellWidth(col, v); + FormatCell(ref cell, value, transConfig, col); + //cell.Value = v; + //cell.Type = t; + CalcCellWidth(col, cell.Value); return cell; } - internal static (string?, string?) FormatCell(object? value, ExcelConfiguration config, ExcelColumnInfo info) + internal static void FormatCell(ref Cell cell, object? value, TransConfiguration transConfig, ExcelColumnInfo info) { var v = string.Empty; var t = "str"; + var config = transConfig.ExcelConfig; if (value == null) { - return (v, t); + return; } if (value is string str) { @@ -126,6 +126,7 @@ internal static (string?, string?) FormatCell(object? value, ExcelConfiguration { t = null; v = ((DateTime)value).ToOADate().ToString(CultureInfo.InvariantCulture); + cell.StyleIndex = info.StyleIndex ?? "2"; } else { @@ -138,8 +139,11 @@ internal static (string?, string?) FormatCell(object? value, ExcelConfiguration v = value.ToString(); } } + cell.Value = v; + cell.Type = t; + var filted = transConfig.NumberFormatColumnFilter(info); + cell.StyleIndex ??= info.StyleIndex ?? (info.NumberFormat || filted ? "1" : null); - return (v, t); } internal static void CalcCellWidth(ExcelColumnInfo col, string? value) diff --git a/src/LightExcel/Utils/RenderCreator.cs b/src/LightExcel/Utils/RenderCreator.cs new file mode 100644 index 0000000..813a8a3 --- /dev/null +++ b/src/LightExcel/Utils/RenderCreator.cs @@ -0,0 +1,34 @@ +using System.Linq.Expressions; + +namespace LightExcel.Utils; + +internal static class RenderCreator where T : IDataRender +{ + static readonly Func function; + static RenderCreator() + { + var ctor = typeof(T).GetConstructors().First(m => m.GetParameters().Length == 1); + ParameterExpression p = Expression.Parameter(typeof(ExcelConfiguration), "config"); + var body = Expression.New(ctor, p); + var lambda = Expression.Lambda>(body, p); + function = lambda.Compile(); + } + + public static IDataRender Create(ExcelConfiguration config) => function.Invoke(config); +} +#if NET6_0_OR_GREATER +internal static class AsyncRenderCreator where TRender : IAsyncDataRender +{ + static readonly Func function; + static AsyncRenderCreator() + { + var ctor = typeof(TRender).GetConstructors().First(m => m.GetParameters().Length == 1); + ParameterExpression p = Expression.Parameter(typeof(ExcelConfiguration), "config"); + var body = Expression.New(ctor, p); + var lambda = Expression.Lambda>(body, p); + function = lambda.Compile(); + } + + public static TRender Create(ExcelConfiguration config) => function.Invoke(config); +} +#endif diff --git a/test/TestProject1/Datas.cs b/test/TestProject1/Datas.cs new file mode 100644 index 0000000..99988c2 --- /dev/null +++ b/test/TestProject1/Datas.cs @@ -0,0 +1,87 @@ +using System.Data; + +namespace TestProject1 +{ + public class Datas + { + public static IEnumerable> DictionarySource() + { + for (int i = 0; i < 10; i++) + { + yield return new Dictionary + { + ["Column0"] = i, + ["Column1"] = DateTime.Now, + ["Column2"] = 0.222, + ["Column3"] = 111, + ["Column4"] = "Hello", + ["Column5"] = "World", + }; + } + } + + public static async IAsyncEnumerable> DictionarySourceAsync() + { + for (int i = 0; i < 10; i++) + { + yield return new Dictionary + { + ["Column0"] = i, + ["Column1"] = DateTime.Now, + ["Column2"] = 0.222, + ["Column3"] = 111, + ["Column4"] = "Hello", + ["Column5"] = "World", + }; + await Task.Delay(5); + } + } + + public static IEnumerable GetEntities() + { + for (int i = 0; i < 10; i++) + { + yield return new Model + { + Index = i + 1, + Birthday = DateTime.Now, + Name = "Hello" + }; + } + } + + public static async IAsyncEnumerable GetEntitiesAsync() + { + for (int i = 0; i < 10; i++) + { + yield return new Model + { + Index = i + 1, + Birthday = DateTime.Now, + Name = "Hello" + }; + await Task.Delay(5); + } + } + + public static DataTable GetDataTable() + { + DataTable dt = new DataTable(); + for (int i = 0; i < 10; i++) + { + dt.Columns.Add($"Column{i}"); + } + for (int i = 0; i < 10; i++) + { + var row = dt.NewRow(); + for (int j = 0; j < 10; j++) + { + //row[j] = $"D({i}x{j})"; + row[j] = $"{i * j}"; + } + dt.Rows.Add(row); + } + return dt; + } + } +} \ No newline at end of file diff --git a/test/TestProject1/Model.cs b/test/TestProject1/Model.cs new file mode 100644 index 0000000..8ad14d0 --- /dev/null +++ b/test/TestProject1/Model.cs @@ -0,0 +1,9 @@ +namespace TestProject1 +{ + public class Model + { + public int Index { get; set; } + public string? Name { get; set; } + public DateTime Birthday { get; set; } + } +} \ No newline at end of file diff --git a/test/TestProject1/OpenXmlExcelReadTest.cs b/test/TestProject1/OpenXmlExcelReadTest.cs index d0621eb..518731a 100644 --- a/test/TestProject1/OpenXmlExcelReadTest.cs +++ b/test/TestProject1/OpenXmlExcelReadTest.cs @@ -31,7 +31,7 @@ public void ExcelReaderTest() public void ExcelReaderTestEntity() { ExcelHelper excel = new ExcelHelper(); - var result = excel.QueryExcel("etest.xlsx", "Sheet1"); + var result = excel.QueryExcel("etest.xlsx", "Sheet1"); } [TestMethod] public void ExcelReaderTestDynamic() diff --git a/test/TestProject1/OpenXmlExcelTest.cs b/test/TestProject1/OpenXmlExcelTest.cs index 4ba6284..5a22281 100644 --- a/test/TestProject1/OpenXmlExcelTest.cs +++ b/test/TestProject1/OpenXmlExcelTest.cs @@ -1,89 +1,60 @@ using LightExcel; -using System.Data; using System.Diagnostics; using System.Text; using System.Xml.Linq; namespace TestProject1 { - public class M - { - public int Index { get; set; } - public string? Name { get; set; } - public DateTime Birthday { get; set; } - } - public class Datas + [TestClass] + public class OpenXmlExcelTest { - public static IEnumerable> DictionarySource() + [TestMethod] + public void CreateExcelDictionary() { - for (int i = 0; i < 10; i++) + var ie = Datas.DictionarySource(); + ExcelHelper excel = new ExcelHelper(); + using var trans = excel.BeginTransaction("dic-test.xlsx", config => { - yield return new Dictionary - { - ["Column1"] = 222, - ["Column2"] = 0.222, - ["Column3"] = 111, - ["Column4"] = "Hello", - ["Column5"] = "World", - }; - } + config.AddNumberFormat("Column2"); + config.AutoWidth = true; + }); + trans.WriteExcel(ie); + Process.Start("powershell", $"start {AppDomain.CurrentDomain.BaseDirectory}"); } - public static IEnumerable GetEntities() + [TestMethod] + public async Task CreateExcelDictionaryAsync() { - for (int i = 0; i < 10; i++) - { - yield return new M - { - Index = i + 1, - Birthday = DateTime.Now, - Name = "Hello" - }; - } - } + var ie = Datas.DictionarySourceAsync(); + ExcelHelper excel = new ExcelHelper(); - public static DataTable GetDataTable() - { - DataTable dt = new DataTable(); - for (int i = 0; i < 10; i++) - { - dt.Columns.Add($"Column{i}"); - } - for (int i = 0; i < 10; i++) + await excel.WriteExcelAsync("async-dic-test.xlsx", ie, config: config => { - var row = dt.NewRow(); - for (int j = 0; j < 10; j++) - { - //row[j] = $"D({i}x{j})"; - row[j] = $"{i * j}"; - } - dt.Rows.Add(row); - } - return dt; + config.AddNumberFormat("Column2"); + config.AutoWidth = true; + }); + Process.Start("powershell", $"start {AppDomain.CurrentDomain.BaseDirectory}"); } - } - [TestClass] - public class OpenXmlExcelTest - { [TestMethod] - public void CreateExcelDictionary() + public void CreateExcelEntity() { - var ie = Datas.DictionarySource(); ExcelHelper excel = new ExcelHelper(); - using var trans = excel.BeginTransaction("1test.xlsx", config => + excel.WriteExcel("entity-test.xlsx", Datas.GetEntities().ToList(), config: c => { - config.AddNumberFormat("Column2"); + c.AutoWidth = true; }); - trans.WriteExcel(ie, "sheet1"); Process.Start("powershell", $"start {AppDomain.CurrentDomain.BaseDirectory}"); } [TestMethod] - public void CreateExcelEntity() + public async Task CreateExcelEntityAsync() { ExcelHelper excel = new ExcelHelper(); - excel.WriteExcel("etest.xlsx", Datas.GetEntities()); + await excel.WriteExcelAsync("async-entity-test.xlsx", Datas.GetEntitiesAsync(), config: c => + { + c.AutoWidth = true; + }); Process.Start("powershell", $"start {AppDomain.CurrentDomain.BaseDirectory}"); } @@ -91,7 +62,7 @@ public void CreateExcelEntity() public void CreateExcelDataTable() { ExcelHelper excel = new(); - excel.WriteExcel("dttest.xlsx", Datas.GetDataTable()); + excel.WriteExcel("dt-test.xlsx", Datas.GetDataTable()); Process.Start("powershell", $"start {AppDomain.CurrentDomain.BaseDirectory}"); } @@ -100,10 +71,23 @@ public void TemplateTest() { var ie = Datas.DictionarySource(); ExcelHelper excel = new ExcelHelper(); - excel.WriteExcelByTemplate("12test.xlsx", "路檢報表格式.xlsx", ie, config: config => + excel.WriteExcelByTemplate("template-test.xlsx", "路檢報表格式.xlsx", ie, config: config => { - + //config.FillWithPlacholder = true; }); + Process.Start("powershell", $"start {AppDomain.CurrentDomain.BaseDirectory}"); + } + + [TestMethod] + public async Task TemplateTestAsync() + { + var ie = Datas.DictionarySourceAsync(); + ExcelHelper excel = new ExcelHelper(); + await excel.WriteExcelByTemplateAsync("async-template-test.xlsx", "路檢報表格式.xlsx", ie, config: config => + { + //config.FillWithPlacholder = true; + }); + Process.Start("powershell", $"start {AppDomain.CurrentDomain.BaseDirectory}"); } } } \ No newline at end of file diff --git a/test/TestProject1/UnitTest1.cs b/test/TestProject1/UnitTest1.cs index 2f48cf7..31079d5 100644 --- a/test/TestProject1/UnitTest1.cs +++ b/test/TestProject1/UnitTest1.cs @@ -1,4 +1,4 @@ -using LightExcel; +using LightExcel; using LightExcel.Attributes; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -32,9 +32,9 @@ public void TestMethod1() class Test01 { - [ExcelColumn(Name = "1")] + [ExcelColumn(Name = "属性1")] public int Prop1 { get; set; } - [ExcelColumn(Name = "2")] + [ExcelColumn(Name = "属性2")] public int Prop2 { get; set; } } @@ -58,7 +58,7 @@ IEnumerable> Ge() public void TestRead() { ExcelHelper excel = new ExcelHelper(); - var reader = excel.ReadExcel(@"D:\Documents\Desktop\ģ.xlsx"); + var reader = excel.ReadExcel(@"D:\Documents\Desktop\导入模板.xlsx"); while (reader.NextResult()) { Console.WriteLine($"================={reader.CurrentSheetName}================"); @@ -87,19 +87,18 @@ public class User public void TestReadModel() { ExcelHelper excel = new ExcelHelper(); - var reader = excel.QueryExcel(@"D:\Documents\Desktop\ģ.xlsx", "Sheet1"); + var reader = excel.QueryExcel(@"D:\Documents\Desktop\导入模板.xlsx", "Sheet1"); } [TestMethod] public void TemplateTest() { - ExcelHelper excel = new ExcelHelper(); + IExcelHelper excel = new ExcelHelper(); const string Path1 = "templateTest.xlsx"; if (File.Exists(Path1)) File.Delete(Path1); using var ms = new MemoryStream(); - using var template = File.Open("template.xlsx", FileMode.Open, FileAccess.Read, FileShare.Read); - excel.WriteExcelByTemplate(ms, template, Ge()); + excel.WriteExcelByTemplate(ms, "template.xlsx", Ge()); File.WriteAllBytes(Path1, ms.ToArray()); Process.Start("powershell", $"start {AppDomain.CurrentDomain.BaseDirectory}"); } @@ -107,7 +106,7 @@ public void TemplateTest() [TestMethod] public void EmbeddedTemplateTest() { - ExcelHelper excel = new ExcelHelper(); + IExcelHelper excel = new ExcelHelper(); const string Path1 = "templateEmbededTest.xlsx"; if (File.Exists(Path1)) File.Delete(Path1);