Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 29 additions & 25 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public int[] SaveAs()

GenerateEndXml();
_archive.Dispose();

return rowsWritten.ToArray();
}

Expand Down Expand Up @@ -193,22 +193,23 @@ private int WriteValues(MiniExcelStreamWriter writer, object values)
WriteEmptySheet(writer);
return 0;
}
var maxColumnIndex = props.Count;

int maxRowIndex;
var maxColumnIndex = props.Count(x => x != null && !x.ExcelIgnore);

writer.Write(WorksheetXml.StartWorksheetWithRelationship);

long dimensionPlaceholderPostition = 0;

// We can write the dimensions directly if the row count is known
if (_configuration.FastMode && !isKnownCount)
if (isKnownCount)
{
dimensionPlaceholderPostition = WriteDimensionPlaceholder(writer);
maxRowIndex = _printHeader ? count + 1 : count;
writer.Write(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, maxColumnIndex)));
}
else if (isKnownCount)
else if (_configuration.FastMode)
{
maxRowIndex = count + (_printHeader ? 1 : 0);
writer.Write(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count)));
dimensionPlaceholderPostition = WriteDimensionPlaceholder(writer);
}

//sheet view
Expand All @@ -219,7 +220,7 @@ private int WriteValues(MiniExcelStreamWriter writer, object values)
long columnWidthsPlaceholderPosition = 0;
if (_configuration.EnableAutoWidth)
{
columnWidthsPlaceholderPosition = WriteColumnWidthPlaceholders(writer, props);
columnWidthsPlaceholderPosition = WriteColumnWidthPlaceholders(writer, maxColumnIndex);
widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props);
}
else
Expand Down Expand Up @@ -263,21 +264,23 @@ private int WriteValues(MiniExcelStreamWriter writer, object values)
}
if (_configuration.EnableAutoWidth)
{
OverWriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths?.Columns);
OverwriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths?.Columns);
}

var toSubtract = _printHeader ? 1 : 0;
return maxRowIndex - toSubtract;
if (_printHeader)
maxRowIndex--;

return maxRowIndex;
}

private static long WriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, ICollection<ExcelColumnInfo> props)
private static long WriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, int count)
{
var placeholderPosition = writer.Flush();
writer.WriteWhitespace(WorksheetXml.GetColumnPlaceholderLength(props.Count));
writer.WriteWhitespace(WorksheetXml.GetColumnPlaceholderLength(count));
return placeholderPosition;
}

private static void OverWriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, long placeholderPosition, IEnumerable<ExcelColumnWidth> columnWidths)
private static void OverwriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, long placeholderPosition, IEnumerable<ExcelColumnWidth> columnWidths)
{
var position = writer.Flush();

Expand All @@ -300,29 +303,30 @@ private static void WriteColumnsWidths(MiniExcelStreamWriter writer, IEnumerable
}
writer.Write(WorksheetXml.Column(column.Index, column.Width));
}
if (!hasWrittenStart)

if (hasWrittenStart)
{
return;
writer.Write(WorksheetXml.EndCols);
}
writer.Write(WorksheetXml.EndCols);
}

private void PrintHeader(MiniExcelStreamWriter writer, List<ExcelColumnInfo> props)
{
var xIndex = 1;
var yIndex = 1;
const int yIndex = 1;
writer.Write(WorksheetXml.StartRow(yIndex));

var xIndex = 1;
foreach (var p in props)
{
if (p == null)
//reason : https://github.com/mini-software/MiniExcel/issues/142
if (p != null)
{
xIndex++; //reason : https://github.com/mini-software/MiniExcel/issues/142
continue;
if (p.ExcelIgnore)
continue;

var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex);
WriteCell(writer, r, columnName: p.ExcelColumnName);
}

var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex);
WriteCell(writer, r, columnName: p.ExcelColumnName);
xIndex++;
}

Expand Down
39 changes: 18 additions & 21 deletions src/MiniExcel/OpenXml/ExcelWidthCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,21 @@ public sealed class ExcelColumnWidth
public int Index { get; set; }
public double Width { get; set; }

internal static IEnumerable<ExcelColumnWidth> FromProps(IEnumerable<ExcelColumnInfo> props, double? minWidth = null)
internal static IEnumerable<ExcelColumnWidth> FromProps(ICollection<ExcelColumnInfo> props, double? minWidth = null)
{
var i = 1;
foreach (var p in props)
{
if (p == null || (p.ExcelColumnWidth == null && minWidth == null))
if (p?.ExcelColumnWidth != null || minWidth != null)
{
i++;
continue;
var colIndex = p?.ExcelColumnIndex + 1;
yield return new ExcelColumnWidth
{
Index = colIndex ?? i,
Width = p?.ExcelColumnWidth ?? minWidth.Value
};
}
var colIndex = p.ExcelColumnIndex == null ? i : p.ExcelColumnIndex.GetValueOrDefault() + 1;
yield return new ExcelColumnWidth
{
Index = colIndex,
Width = p.ExcelColumnWidth ?? minWidth.Value,
};

i++;
}
}
Expand All @@ -38,21 +37,20 @@ public sealed class ExcelWidthCollection

public IEnumerable<ExcelColumnWidth> Columns => _columnWidths.Values;

internal ExcelWidthCollection(double minWidth, double maxWidth, IEnumerable<ExcelColumnInfo> props)
internal ExcelWidthCollection(double minWidth, double maxWidth, ICollection<ExcelColumnInfo> props)
{
_maxWidth = maxWidth;
_columnWidths = ExcelColumnWidth.FromProps(props, minWidth).ToDictionary(x => x.Index);
}

public void AdjustWidth(int columnIndex, string columnValue)
{
if (string.IsNullOrEmpty(columnValue) || !_columnWidths.TryGetValue(columnIndex, out var currentWidth))
if (!string.IsNullOrEmpty(columnValue)
&& _columnWidths.TryGetValue(columnIndex, out var currentWidth))
{
return;
var adjustedWidth = Math.Max(currentWidth.Width, GetApproximateTextWidth(columnValue.Length));
currentWidth.Width = Math.Min(_maxWidth, adjustedWidth);
}

var adjustedWidth = Math.Max(currentWidth.Width, GetApproximateRequiredCalibriWidth(columnValue.Length));
currentWidth.Width = Math.Min(_maxWidth, adjustedWidth);
}

/// <summary>
Expand All @@ -61,13 +59,12 @@ public void AdjustWidth(int columnIndex, string columnValue)
/// <remarks>
/// Rounds the result to 2 decimal places.
/// </remarks>
public static double GetApproximateRequiredCalibriWidth(int textLength)
public static double GetApproximateTextWidth(int textLength)
{
double characterWidthFactor = 1.2; // Estimated factor for Calibri, 11pt
double padding = 2; // Add some padding for extra spacing

double excelColumnWidth = (textLength * characterWidthFactor) + padding;
const double characterWidthFactor = 1.2; // Estimated factor for Calibri, 11pt
const double padding = 2; // Add some padding for extra spacing

var excelColumnWidth = (textLength * characterWidthFactor) + padding;
return Math.Round(excelColumnWidth, 2);
}
}
Expand Down
33 changes: 19 additions & 14 deletions src/MiniExcel/Utils/CustomPropertyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ internal static List<ExcelColumnInfo> SortCustomProps(List<ExcelColumnInfo> prop

internal static List<ExcelColumnInfo> GetExcelCustomPropertyInfos(Type type, string[] keys, Configuration configuration)
{
var flags = BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance;
const BindingFlags flags = BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance;
var props = GetExcelPropertyInfo(type, flags, configuration)
.Where(prop => prop.Property.Info.GetSetMethod() != null // why not .Property.CanWrite? because it will use private setter
&& !prop.Property.Info.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore)
Expand Down Expand Up @@ -154,9 +154,9 @@ internal static string DescriptionAttr(Type type, object source)
{
var name = source?.ToString();
return type
.GetField(name)? //For some database dirty data, there may be no way to change to the correct enumeration, will return NULL
.GetCustomAttribute<DescriptionAttribute>(false)?
.Description
.GetField(name) //For some database dirty data, there may be no way to change to the correct enumeration, will return NULL
?.GetCustomAttribute<DescriptionAttribute>(false)
?.Description
?? name;
}

Expand Down Expand Up @@ -243,8 +243,10 @@ internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration conf
internal static List<ExcelColumnInfo> GetDictionaryColumnInfo(IDictionary<string, object> dicString, IDictionary dic, Configuration configuration)
{
var props = new List<ExcelColumnInfo>();
var keys = dicString?.Keys.ToList() ?? dic?.Keys
?? throw new NotSupportedException();

var keys = dicString?.Keys.ToList()
?? dic?.Keys
?? throw new NotSupportedException();

foreach (var key in keys)
{
Expand All @@ -261,9 +263,7 @@ internal static void SetDictionaryColumnInfo(List<ExcelColumnInfo> props, object
ExcelColumnName = key?.ToString()
};

// TODO:Dictionary value type is not fiexed
//var _t =
//var gt = Nullable.GetUnderlyingType(p.PropertyType);
// TODO:Dictionary value type is not fixed
var isIgnore = false;
if (configuration.DynamicColumns != null && configuration.DynamicColumns.Length > 0)
{
Expand Down Expand Up @@ -328,9 +328,10 @@ internal static List<ExcelColumnInfo> GetColumnInfoFromValue(object value, Confi
}
}

private static bool ValueIsNeededToDetermineProperties(Type type) => type == typeof(object)
|| typeof(IDictionary<string, object>).IsAssignableFrom(type)
|| typeof(IDictionary).IsAssignableFrom(type);
private static bool ValueIsNeededToDetermineProperties(Type type) =>
typeof(object) == type ||
typeof(IDictionary<string, object>).IsAssignableFrom(type) ||
typeof(IDictionary).IsAssignableFrom(type);

internal static ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName, Configuration configuration)
{
Expand All @@ -346,16 +347,20 @@ internal static ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string co
var dynamicColumn = configuration.DynamicColumns
.SingleOrDefault(col => string.Equals(col.Key, columnName, StringComparison.OrdinalIgnoreCase));

if (dynamicColumn == null || dynamicColumn.Ignore)
if (dynamicColumn == null)
return prop;

prop.Nullable = true;
prop.ExcelIgnore = dynamicColumn.Ignore;
prop.ExcelColumnType = dynamicColumn.Type;
prop.ExcelColumnIndex = dynamicColumn.Index;
prop.ExcelColumnWidth = dynamicColumn.Width;
prop.CustomFormatter = dynamicColumn.CustomFormatter;

if (dynamicColumn.Index > -1)
{
prop.ExcelColumnIndex = dynamicColumn.Index;
}

if (dynamicColumn.Format != null)
{
prop.ExcelFormat = dynamicColumn.Format;
Expand Down
34 changes: 11 additions & 23 deletions src/MiniExcel/WriteAdapter/DataReaderWriteAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,8 @@ public List<ExcelColumnInfo> GetColumns()
for (var i = 0; i < _reader.FieldCount; i++)
{
var columnName = _reader.GetName(i);

if (!_configuration.DynamicColumnFirst)
{
var prop = CustomPropertyHelper.GetColumnInfosFromDynamicConfiguration(columnName, _configuration);
props.Add(prop);
continue;
}

if (_configuration
.DynamicColumns
.Any(a => string.Equals(
a.Key,
columnName,
StringComparison.OrdinalIgnoreCase)))

if (!_configuration.DynamicColumnFirst ||
_configuration.DynamicColumns.Any(d => string.Equals(d.Key, columnName, StringComparison.OrdinalIgnoreCase)))
{
var prop = CustomPropertyHelper.GetColumnInfosFromDynamicConfiguration(columnName, _configuration);
props.Add(prop);
Expand All @@ -64,16 +51,17 @@ public IEnumerable<IEnumerable<CellWriteInfo>> GetRows(List<ExcelColumnInfo> pro

private IEnumerable<CellWriteInfo> GetRowValues(List<ExcelColumnInfo> props)
{
for (int i = 0, column = 1; i < _reader.FieldCount; i++, column++)
var column = 1;
for (int i = 0; i < _reader.FieldCount; i++)
{
if (_configuration.DynamicColumnFirst)
{
var columnIndex = _reader.GetOrdinal(props[i].Key.ToString());
yield return new CellWriteInfo(_reader.GetValue(columnIndex), column, props[i]);
}
else
var prop = props[i];
if (prop != null && !prop.ExcelIgnore)
{
yield return new CellWriteInfo(_reader.GetValue(i), column, props[i]);
var columnIndex = _configuration.DynamicColumnFirst
? _reader.GetOrdinal(prop.Key.ToString()) : i;

yield return new CellWriteInfo(_reader.GetValue(columnIndex), column, prop);
column++;
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions tests/MiniExcelTests/MiniExcelAutoAdjustWidthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,16 +146,16 @@
private static void AssertExpectedWidth(string path, OpenXmlConfiguration configuration)
{
using var document = SpreadsheetDocument.Open(path, false);
var worksheetPart = document.WorkbookPart.WorksheetParts.First();

Check warning on line 149 in tests/MiniExcelTests/MiniExcelAutoAdjustWidthTests.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

var columns = worksheetPart.Worksheet.GetFirstChild<Columns>();
Assert.False(columns == null, "No column width information was written.");
foreach (var column in columns.Elements<Column>())
{
var expectedWidth = column.Min.Value switch

Check warning on line 155 in tests/MiniExcelTests/MiniExcelAutoAdjustWidthTests.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
{
1 => ExcelWidthCollection.GetApproximateRequiredCalibriWidth(AutoAdjustTestParameters.column1MaxStringLength),
2 => ExcelWidthCollection.GetApproximateRequiredCalibriWidth(AutoAdjustTestParameters.column2MaxStringLength),
1 => ExcelWidthCollection.GetApproximateTextWidth(AutoAdjustTestParameters.column1MaxStringLength),
2 => ExcelWidthCollection.GetApproximateTextWidth(AutoAdjustTestParameters.column2MaxStringLength),
3 => configuration.MinWidth,
4 => configuration.MaxWidth,
_ => throw new Exception("Unexpected column"),
Expand Down Expand Up @@ -198,8 +198,8 @@
{
EnableAutoWidth = true,
FastMode = true,
MinWidth = ExcelWidthCollection.GetApproximateRequiredCalibriWidth(minStringLength),
MaxWidth = ExcelWidthCollection.GetApproximateRequiredCalibriWidth(maxStringLength)
MinWidth = ExcelWidthCollection.GetApproximateTextWidth(minStringLength),
MaxWidth = ExcelWidthCollection.GetApproximateTextWidth(maxStringLength)
};
}
}
Loading
Loading