Skip to content

Commit

Permalink
Write auto column width (#695)
Browse files Browse the repository at this point in the history
* Add AutoWidth functionality for async write

* Add MiniExcelAutoAdjustWidthTests

* Implement autowidth for sync write
  • Loading branch information
Discolai authored Nov 22, 2024
1 parent 8e41121 commit 0b15822
Show file tree
Hide file tree
Showing 10 changed files with 523 additions and 43 deletions.
14 changes: 12 additions & 2 deletions src/MiniExcel/OpenXml/Constants/WorksheetXml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Globalization;
using MiniExcelLibs.Attributes;
using System.Linq;

namespace MiniExcelLibs.OpenXml.Constants
{
Expand Down Expand Up @@ -46,8 +47,17 @@ internal static string StartRow(int rowIndex)
internal const string EndRow = "</x:row>";

internal const string StartCols = "<x:cols>";
internal static string Column(int? colIndex, double? columnWidth)
=> $@"<x:col min=""{colIndex.GetValueOrDefault() + 1}"" max=""{colIndex.GetValueOrDefault() + 1}"" width=""{columnWidth?.ToString(CultureInfo.InvariantCulture)}"" customWidth=""1"" />";
internal static string Column(int colIndex, double columnWidth)
=> $@"<x:col min=""{colIndex}"" max=""{colIndex}"" width=""{columnWidth.ToString(CultureInfo.InvariantCulture)}"" customWidth=""1"" />";


private static readonly int _maxColumnLength = Column(int.MaxValue, double.MaxValue).Length;

public static int GetColumnPlaceholderLength(int columnCount)
{
return StartCols.Length + (_maxColumnLength * columnCount) + EndCols.Length;
}

internal const string EndCols = "</x:cols>";

internal static string EmptyCell(string cellReference, string styleIndex)
Expand Down
105 changes: 84 additions & 21 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO.Compression;
using System.Linq;
using System.Text;
Expand Down Expand Up @@ -104,6 +105,8 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr
var yIndex = 1;
int maxColumnIndex;
int maxRowIndex;
ExcelWidthCollection widths = null;
long columnWidthsPlaceholderPosition = 0;
{
if (_configuration.FastMode)
{
Expand All @@ -122,7 +125,15 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr
//sheet view
await writer.WriteAsync(GetSheetViews());

await WriteColumnsWidthsAsync(writer, props);
if (_configuration.EnableAutoWidth)
{
columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props);
widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props);
}
else
{
await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props));
}

await writer.WriteAsync(WorksheetXml.StartSheetData);
int fieldCount = reader.FieldCount;
Expand All @@ -139,7 +150,7 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr
for (int i = 0; i < fieldCount; i++)
{
var cellValue = reader.GetValue(i);
await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[i]);
await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[i], widths);
xIndex++;
}
await writer.WriteAsync(WorksheetXml.EndRow);
Expand All @@ -163,6 +174,10 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr
{
await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition);
}
if (_configuration.EnableAutoWidth)
{
await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns);
}
}

private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter writer, IEnumerable values)
Expand Down Expand Up @@ -248,7 +263,17 @@ private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter wri
await writer.WriteAsync(GetSheetViews());

//cols:width
await WriteColumnsWidthsAsync(writer, props);
ExcelWidthCollection widths = null;
long columnWidthsPlaceholderPosition = 0;
if (_configuration.EnableAutoWidth)
{
columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props);
widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props);
}
else
{
await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props));
}

//header
await writer.WriteAsync(WorksheetXml.StartSheetData);
Expand All @@ -266,13 +291,13 @@ private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter wri
switch (mode)
{
case "IDictionary<string, object>": //Dapper Row
maxRowIndex = await GenerateSheetByColumnInfoAsync<IDictionary<string, object>>(writer, enumerator, props, xIndex, yIndex);
maxRowIndex = await GenerateSheetByColumnInfoAsync<IDictionary<string, object>>(writer, enumerator, props, widths, xIndex, yIndex);
break;
case "IDictionary":
maxRowIndex = await GenerateSheetByColumnInfoAsync<IDictionary>(writer, enumerator, props, xIndex, yIndex);
maxRowIndex = await GenerateSheetByColumnInfoAsync<IDictionary>(writer, enumerator, props, widths, xIndex, yIndex);
break;
case "Properties":
maxRowIndex = await GenerateSheetByColumnInfoAsync<object>(writer, enumerator, props, xIndex, yIndex);
maxRowIndex = await GenerateSheetByColumnInfoAsync<object>(writer, enumerator, props, widths, xIndex, yIndex);
break;
default:
throw new NotImplementedException($"Type {values.GetType().FullName} is not implemented. Please open an issue.");
Expand All @@ -293,6 +318,10 @@ private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter wri
{
await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition);
}
if (_configuration.EnableAutoWidth)
{
await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns);
}
}

private async Task GenerateSheetByDataTableAsync(MiniExcelAsyncStreamWriter writer, DataTable value)
Expand All @@ -318,7 +347,17 @@ private async Task GenerateSheetByDataTableAsync(MiniExcelAsyncStreamWriter writ
//sheet view
await writer.WriteAsync(GetSheetViews());

await WriteColumnsWidthsAsync(writer, props);
ExcelWidthCollection widths = null;
long columnWidthsPlaceholderPosition = 0;
if (_configuration.EnableAutoWidth)
{
columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props);
widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props);
}
else
{
await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props));
}

await writer.WriteAsync(WorksheetXml.StartSheetData);
if (_printHeader)
Expand All @@ -344,7 +383,7 @@ private async Task GenerateSheetByDataTableAsync(MiniExcelAsyncStreamWriter writ
for (int j = 0; j < value.Columns.Count; j++)
{
var cellValue = value.Rows[i][j];
await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[j]);
await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[j], widths);
xIndex++;
}
await writer.WriteAsync(WorksheetXml.EndRow);
Expand All @@ -357,25 +396,48 @@ private async Task GenerateSheetByDataTableAsync(MiniExcelAsyncStreamWriter writ
{
await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex)));
}
if (_configuration.EnableAutoWidth)
{
await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns);
}

await writer.WriteAsync(WorksheetXml.EndWorksheet);
}

private static async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IEnumerable<ExcelColumnInfo> props)
private async Task<long> WriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, ICollection<ExcelColumnInfo> props)
{
var ecwProps = props.Where(x => x?.ExcelColumnWidth != null).ToList();
if (ecwProps.Count <= 0)
{
return;
}
var placeholderPosition = await writer.FlushAsync();
await writer.WriteWhitespaceAsync(WorksheetXml.GetColumnPlaceholderLength(props.Count));
return placeholderPosition;
}

await writer.WriteAsync(WorksheetXml.StartCols);
private async Task OverWriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, long placeholderPosition, IEnumerable<ExcelColumnWidth> columnWidths)
{
var position = await writer.FlushAsync();

foreach (var p in ecwProps)
writer.SetPosition(placeholderPosition);
await WriteColumnsWidthsAsync(writer, columnWidths);

await writer.FlushAsync();
writer.SetPosition(position);
}

private async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IEnumerable<ExcelColumnWidth> columnWidths)
{
var hasWrittenStart = false;
foreach (var column in columnWidths)
{
await writer.WriteAsync(WorksheetXml.Column(p.ExcelColumnIndex, p.ExcelColumnWidth));
if (!hasWrittenStart)
{
await writer.WriteAsync(WorksheetXml.StartCols);
hasWrittenStart = true;
}
await writer.WriteAsync(WorksheetXml.Column(column.Index, column.Width));
}
if (!hasWrittenStart)
{
return;
}

await writer.WriteAsync(WorksheetXml.EndCols);
}

Expand All @@ -401,7 +463,7 @@ private static async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, Li
await writer.WriteAsync(WorksheetXml.EndRow);
}

private async Task<int> GenerateSheetByColumnInfoAsync<T>(MiniExcelAsyncStreamWriter writer, IEnumerator value, List<ExcelColumnInfo> props, int xIndex = 1, int yIndex = 1)
private async Task<int> GenerateSheetByColumnInfoAsync<T>(MiniExcelAsyncStreamWriter writer, IEnumerator value, List<ExcelColumnInfo> props, ExcelWidthCollection widthCollection, int xIndex = 1, int yIndex = 1)
{
var isDic = typeof(T) == typeof(IDictionary);
var isDapperRow = typeof(T) == typeof(IDictionary<string, object>);
Expand Down Expand Up @@ -436,7 +498,7 @@ private async Task<int> GenerateSheetByColumnInfoAsync<T>(MiniExcelAsyncStreamWr
cellValue = p.Property.GetValue(v);
}

await WriteCellAsync(writer, yIndex, cellIndex, cellValue, p);
await WriteCellAsync(writer, yIndex, cellIndex, cellValue, p, widthCollection);

cellIndex++;
}
Expand All @@ -453,7 +515,7 @@ private static async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, stri
await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", "1", ExcelOpenXmlUtils.EncodeXML(columnName)));
}

private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo p)
private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo p, ExcelWidthCollection widthCollection)
{
var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex);
var valueIsNull = value is null || value is DBNull;
Expand All @@ -474,6 +536,7 @@ private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, int rowInde
/*Prefix and suffix blank space will lost after SaveAs #294*/
var preserveSpace = cellValue != null && (cellValue.StartsWith(" ", StringComparison.Ordinal) || cellValue.EndsWith(" ", StringComparison.Ordinal));
await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, styleIndex, cellValue, preserveSpace: preserveSpace, columnType: columnType));
widthCollection?.AdjustWidth(cellIndex, cellValue);
}

private async Task GenerateEndXmlAsync(CancellationToken cancellationToken)
Expand Down
Loading

0 comments on commit 0b15822

Please sign in to comment.