Skip to content

Commit 0b15822

Browse files
authored
Write auto column width (#695)
* Add AutoWidth functionality for async write * Add MiniExcelAutoAdjustWidthTests * Implement autowidth for sync write
1 parent 8e41121 commit 0b15822

File tree

10 files changed

+523
-43
lines changed

10 files changed

+523
-43
lines changed

src/MiniExcel/OpenXml/Constants/WorksheetXml.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Globalization;
22
using MiniExcelLibs.Attributes;
3+
using System.Linq;
34

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

4849
internal const string StartCols = "<x:cols>";
49-
internal static string Column(int? colIndex, double? columnWidth)
50-
=> $@"<x:col min=""{colIndex.GetValueOrDefault() + 1}"" max=""{colIndex.GetValueOrDefault() + 1}"" width=""{columnWidth?.ToString(CultureInfo.InvariantCulture)}"" customWidth=""1"" />";
50+
internal static string Column(int colIndex, double columnWidth)
51+
=> $@"<x:col min=""{colIndex}"" max=""{colIndex}"" width=""{columnWidth.ToString(CultureInfo.InvariantCulture)}"" customWidth=""1"" />";
52+
53+
54+
private static readonly int _maxColumnLength = Column(int.MaxValue, double.MaxValue).Length;
55+
56+
public static int GetColumnPlaceholderLength(int columnCount)
57+
{
58+
return StartCols.Length + (_maxColumnLength * columnCount) + EndCols.Length;
59+
}
60+
5161
internal const string EndCols = "</x:cols>";
5262

5363
internal static string EmptyCell(string cellReference, string styleIndex)

src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections;
77
using System.Collections.Generic;
88
using System.Data;
9+
using System.Globalization;
910
using System.IO.Compression;
1011
using System.Linq;
1112
using System.Text;
@@ -104,6 +105,8 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr
104105
var yIndex = 1;
105106
int maxColumnIndex;
106107
int maxRowIndex;
108+
ExcelWidthCollection widths = null;
109+
long columnWidthsPlaceholderPosition = 0;
107110
{
108111
if (_configuration.FastMode)
109112
{
@@ -122,7 +125,15 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr
122125
//sheet view
123126
await writer.WriteAsync(GetSheetViews());
124127

125-
await WriteColumnsWidthsAsync(writer, props);
128+
if (_configuration.EnableAutoWidth)
129+
{
130+
columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props);
131+
widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props);
132+
}
133+
else
134+
{
135+
await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props));
136+
}
126137

127138
await writer.WriteAsync(WorksheetXml.StartSheetData);
128139
int fieldCount = reader.FieldCount;
@@ -139,7 +150,7 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr
139150
for (int i = 0; i < fieldCount; i++)
140151
{
141152
var cellValue = reader.GetValue(i);
142-
await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[i]);
153+
await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[i], widths);
143154
xIndex++;
144155
}
145156
await writer.WriteAsync(WorksheetXml.EndRow);
@@ -163,6 +174,10 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr
163174
{
164175
await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition);
165176
}
177+
if (_configuration.EnableAutoWidth)
178+
{
179+
await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns);
180+
}
166181
}
167182

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

250265
//cols:width
251-
await WriteColumnsWidthsAsync(writer, props);
266+
ExcelWidthCollection widths = null;
267+
long columnWidthsPlaceholderPosition = 0;
268+
if (_configuration.EnableAutoWidth)
269+
{
270+
columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props);
271+
widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props);
272+
}
273+
else
274+
{
275+
await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props));
276+
}
252277

253278
//header
254279
await writer.WriteAsync(WorksheetXml.StartSheetData);
@@ -266,13 +291,13 @@ private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter wri
266291
switch (mode)
267292
{
268293
case "IDictionary<string, object>": //Dapper Row
269-
maxRowIndex = await GenerateSheetByColumnInfoAsync<IDictionary<string, object>>(writer, enumerator, props, xIndex, yIndex);
294+
maxRowIndex = await GenerateSheetByColumnInfoAsync<IDictionary<string, object>>(writer, enumerator, props, widths, xIndex, yIndex);
270295
break;
271296
case "IDictionary":
272-
maxRowIndex = await GenerateSheetByColumnInfoAsync<IDictionary>(writer, enumerator, props, xIndex, yIndex);
297+
maxRowIndex = await GenerateSheetByColumnInfoAsync<IDictionary>(writer, enumerator, props, widths, xIndex, yIndex);
273298
break;
274299
case "Properties":
275-
maxRowIndex = await GenerateSheetByColumnInfoAsync<object>(writer, enumerator, props, xIndex, yIndex);
300+
maxRowIndex = await GenerateSheetByColumnInfoAsync<object>(writer, enumerator, props, widths, xIndex, yIndex);
276301
break;
277302
default:
278303
throw new NotImplementedException($"Type {values.GetType().FullName} is not implemented. Please open an issue.");
@@ -293,6 +318,10 @@ private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter wri
293318
{
294319
await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition);
295320
}
321+
if (_configuration.EnableAutoWidth)
322+
{
323+
await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns);
324+
}
296325
}
297326

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

321-
await WriteColumnsWidthsAsync(writer, props);
350+
ExcelWidthCollection widths = null;
351+
long columnWidthsPlaceholderPosition = 0;
352+
if (_configuration.EnableAutoWidth)
353+
{
354+
columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props);
355+
widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props);
356+
}
357+
else
358+
{
359+
await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props));
360+
}
322361

323362
await writer.WriteAsync(WorksheetXml.StartSheetData);
324363
if (_printHeader)
@@ -344,7 +383,7 @@ private async Task GenerateSheetByDataTableAsync(MiniExcelAsyncStreamWriter writ
344383
for (int j = 0; j < value.Columns.Count; j++)
345384
{
346385
var cellValue = value.Rows[i][j];
347-
await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[j]);
386+
await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[j], widths);
348387
xIndex++;
349388
}
350389
await writer.WriteAsync(WorksheetXml.EndRow);
@@ -357,25 +396,48 @@ private async Task GenerateSheetByDataTableAsync(MiniExcelAsyncStreamWriter writ
357396
{
358397
await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex)));
359398
}
399+
if (_configuration.EnableAutoWidth)
400+
{
401+
await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns);
402+
}
360403

361404
await writer.WriteAsync(WorksheetXml.EndWorksheet);
362405
}
363406

364-
private static async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IEnumerable<ExcelColumnInfo> props)
407+
private async Task<long> WriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, ICollection<ExcelColumnInfo> props)
365408
{
366-
var ecwProps = props.Where(x => x?.ExcelColumnWidth != null).ToList();
367-
if (ecwProps.Count <= 0)
368-
{
369-
return;
370-
}
409+
var placeholderPosition = await writer.FlushAsync();
410+
await writer.WriteWhitespaceAsync(WorksheetXml.GetColumnPlaceholderLength(props.Count));
411+
return placeholderPosition;
412+
}
371413

372-
await writer.WriteAsync(WorksheetXml.StartCols);
414+
private async Task OverWriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, long placeholderPosition, IEnumerable<ExcelColumnWidth> columnWidths)
415+
{
416+
var position = await writer.FlushAsync();
373417

374-
foreach (var p in ecwProps)
418+
writer.SetPosition(placeholderPosition);
419+
await WriteColumnsWidthsAsync(writer, columnWidths);
420+
421+
await writer.FlushAsync();
422+
writer.SetPosition(position);
423+
}
424+
425+
private async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter writer, IEnumerable<ExcelColumnWidth> columnWidths)
426+
{
427+
var hasWrittenStart = false;
428+
foreach (var column in columnWidths)
375429
{
376-
await writer.WriteAsync(WorksheetXml.Column(p.ExcelColumnIndex, p.ExcelColumnWidth));
430+
if (!hasWrittenStart)
431+
{
432+
await writer.WriteAsync(WorksheetXml.StartCols);
433+
hasWrittenStart = true;
434+
}
435+
await writer.WriteAsync(WorksheetXml.Column(column.Index, column.Width));
436+
}
437+
if (!hasWrittenStart)
438+
{
439+
return;
377440
}
378-
379441
await writer.WriteAsync(WorksheetXml.EndCols);
380442
}
381443

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

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

439-
await WriteCellAsync(writer, yIndex, cellIndex, cellValue, p);
501+
await WriteCellAsync(writer, yIndex, cellIndex, cellValue, p, widthCollection);
440502

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

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

479542
private async Task GenerateEndXmlAsync(CancellationToken cancellationToken)

0 commit comments

Comments
 (0)