using ClosedXML.Excel; using System.Globalization; using System.IO; namespace zero.Utils; public class ExcelCreator : ITableCreator { private MemoryStream Stream { get; set; } private XLWorkbook Workbook { get; set; } private IEnumerable> Columns { get; set; } private const string DATE_FORMAT = "dd. MM. yyyy"; private const string DATETIME_FORMAT = "dd. MM. yyyy, HH:ii"; private const string BOOL_YES = "yes"; private const string BOOL_NO = "no"; private const string LINK_NAME = "Link"; public const string CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; private CultureInfo Culture = null; public ExcelCreator(IEnumerable> columns) { Columns = columns; } /// /// Create a readable XLSX stream from a source enumerable /// public MemoryStream CreateStream(IEnumerable source, CultureInfo culture) { Culture = culture; Workbook = Create(true, out int lineNumber); foreach (T item in source) { AddRow(Workbook.Worksheets.First(), lineNumber++, Columns, item); } Stream = new MemoryStream(); Workbook.SaveAs(Stream); Stream.Position = 0; return Stream; } /// /// Create a readable XLSX stream from a source enumerable /// public async Task CreateStream(IAsyncEnumerable source, CultureInfo culture, CancellationToken token = default) { Culture = culture; Workbook = Create(true, out int lineNumber); await foreach (T item in source.WithCancellation(token)) { AddRow(Workbook.Worksheets.First(), lineNumber++, Columns, item); } Stream = new MemoryStream(); Workbook.SaveAs(Stream); Stream.Position = 0; return Stream; } /// /// Create a XLSX from a source enumerable /// protected XLWorkbook Create(IEnumerable source, bool headerRow = true) { XLWorkbook workbook = new XLWorkbook(); IXLWorksheet worksheet = workbook.Worksheets.Add("Data"); int lineNumber = 1; // add metadata + defaults workbook.RowHeight = 20; // add header if (headerRow) { AddRow(worksheet, lineNumber++, Columns, default, true); worksheet.Row(1).Height = 30; } // set column widths for (int i = 0; i < Columns.Count(); i++) { TableColumn column = Columns.ElementAt(i); worksheet.Column(i + 1).Width = column.Width; } // add rows foreach (T item in source) { AddRow(worksheet, lineNumber++, Columns, item); } return workbook; } /// /// Create a XLSX from a source enumerable /// protected XLWorkbook Create(bool headerRow, out int lineNumber) { XLWorkbook workbook = new XLWorkbook(); IXLWorksheet worksheet = workbook.Worksheets.Add("Data"); lineNumber = 1; // add metadata + defaults workbook.RowHeight = 20; // add header if (headerRow) { AddRow(worksheet, lineNumber++, Columns, default, true); worksheet.Row(1).Height = 30; } // set column widths for (int i = 0; i < Columns.Count(); i++) { TableColumn column = Columns.ElementAt(i); worksheet.Column(i + 1).Width = column.Width; } return workbook; } /// /// Appends a line to the XLSX /// protected void AddRow(IXLWorksheet builder, int lineNumber, IEnumerable> columns, T item, bool isHeader = false) { int index = 0; foreach (TableColumn column in columns) { object value = isHeader ? column.Name : (column.FieldSelector != null && column.CanRender(item) ? column.FieldSelector(item) : String.Empty); object property = MapProperty(value, column, out XLDataType dataType); IXLCell cell = builder.Cell(lineNumber, index + 1) .SetValue(property) .SetDataType(dataType); if (isHeader) { cell.Style.Font.SetBold(); cell.Style.Fill.BackgroundColor = XLColor.WhiteSmoke; } cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center; if (!isHeader && column.ColumnType == TableColumnType.Link) { cell.SetValue(LINK_NAME); cell.Hyperlink = new XLHyperlink(value.ToString()); } if (column.ColumnType == TableColumnType.Currency) { cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right; } index += 1; } if (!isHeader) { builder.Row(lineNumber).Height = 20; } } /// /// Map a property based on its type /// protected object MapProperty(object value, TableColumn column, out XLDataType dataType) { dataType = XLDataType.Text; if (value == null) { return String.Empty; } if (value is bool) { return (bool)value ? BOOL_YES : BOOL_NO; } else if (value is decimal || value is float || value is double) { dataType = XLDataType.Number; decimal result = Decimal.Round(Convert.ToDecimal(value), 2); if (column.ColumnType == TableColumnType.Currency) { dataType = XLDataType.Text; return result.ToString("C2", Culture); } return result; } else if (value is int) { dataType = XLDataType.Number; return value; } else if (value is DateTime) { return ((DateTime)value).ToString(DATE_FORMAT); } else if (value is DateTime?) { var val = (DateTime?)value; return val.HasValue ? val.Value.ToString(DATE_FORMAT) : String.Empty; } else if (value is DateTimeOffset) { return ((DateTimeOffset)value).ToString(DATE_FORMAT); } else if (value is DateTimeOffset?) { var val = (DateTimeOffset?)value; return val.HasValue ? val.Value.ToString(DATE_FORMAT) : String.Empty; } return value; } public void Dispose() { Stream?.Dispose(); Workbook?.Dispose(); } }