Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom DateTime format #616

Merged
merged 12 commits into from
Jun 13, 2024
3 changes: 3 additions & 0 deletions src/MiniExcel/Attributes/ExcelColumnAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class ExcelColumnAttribute : Attribute
private int _index = -1;
private string _xName;

internal int FormatId { get; set; } = -1;

public string Name { get; set; }

public string[] Aliases { get; set; }
Expand Down Expand Up @@ -52,6 +54,7 @@ private void Init(int index, string columnName = null)
public class DynamicExcelColumn : ExcelColumnAttribute
{
public string Key { get; set; }

public DynamicExcelColumn(string key)
{
Key = key;
Expand Down
74 changes: 70 additions & 4 deletions src/MiniExcel/OpenXml/Constants/ExcelXml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
using MiniExcelLibs.OpenXml.Models;
using MiniExcelLibs.Attributes;
using MiniExcelLibs.OpenXml.Models;
using MiniExcelLibs.Utils;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MiniExcelLibs.OpenXml.Constants
{
Expand Down Expand Up @@ -52,10 +57,21 @@ static ExcelXml()
</x:cellXfs>
</x:styleSheet>";

internal static readonly string DefaultStylesXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
#region StyleSheet

private const int startUpNumFmts = 1;
private const string NumFmtsToken = "{{numFmts}}";
private const string NumFmtsCountToken = "{{numFmtCount}}";

private const int startUpCellXfs = 5;
private const string cellXfsToken = "{{cellXfs}}";
private const string cellXfsCountToken = "{{cellXfsCount}}";

internal static readonly string DefaultStylesXml = $@"<?xml version=""1.0"" encoding=""utf-8""?>
<x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:numFmts count=""1"">
<x:numFmts count=""{NumFmtsCountToken}"">
<x:numFmt numFmtId=""0"" formatCode="""" />
{NumFmtsToken}
</x:numFmts>
<x:fonts count=""2"">
<x:font>
Expand Down Expand Up @@ -133,7 +149,7 @@ static ExcelXml()
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
</x:cellStyleXfs>
<x:cellXfs count=""4"">
<x:cellXfs count=""{cellXfsCountToken}"">
<x:xf></x:xf>
<x:xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""0"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""left"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
Expand All @@ -150,12 +166,15 @@ static ExcelXml()
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyBorder=""1"" applyAlignment=""1"">
<x:alignment horizontal=""fill""/>
</x:xf>
{cellXfsToken}
</x:cellXfs>
<x:cellStyles count=""1"">
<x:cellStyle name=""Normal"" xfId=""0"" builtinId=""0"" />
</x:cellStyles>
</x:styleSheet>";

#endregion

internal static readonly string DefaultWorkbookXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<x:workbook xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships""
xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
Expand Down Expand Up @@ -231,5 +250,52 @@ internal static string DrawingXml(FileDto file, int fileIndex)

internal static string Sheet(SheetDto sheetDto, int sheetId)
=> $@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(sheetDto.Name)}"" sheetId=""{sheetId}""{(string.IsNullOrWhiteSpace(sheetDto.State) ? string.Empty : $" state=\"{sheetDto.State}\"")} r:id=""{sheetDto.ID}"" />";

internal static string SetupStyleXml(string styleXml, ICollection<ExcelColumnAttribute> columns)
{
const int numFmtIndex = 166;

var sb = new StringBuilder(styleXml);
var columnsToApply = GenerateStyleIds(columns);

var numFmts = columnsToApply.Select((x, i) =>
{
return new
{
numFmt =
$@"<x:numFmt numFmtId=""{numFmtIndex + i}"" formatCode=""{x.Format}"" />",

cellXfs =
$@"<x:xf numFmtId=""{numFmtIndex + i}"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""general"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>"
};
}).ToArray();

sb.Replace(NumFmtsToken, string.Join(string.Empty, numFmts.Select(x => x.numFmt)));
sb.Replace(NumFmtsCountToken, (startUpNumFmts + numFmts.Length).ToString());

sb.Replace(cellXfsToken, string.Join(string.Empty, numFmts.Select(x => x.cellXfs)));
sb.Replace(cellXfsCountToken, (5 + numFmts.Length).ToString());
return sb.ToString();
}

private static IEnumerable<ExcelColumnAttribute> GenerateStyleIds(ICollection<ExcelColumnAttribute> dynamicColumns)
{
if (dynamicColumns == null)
yield break;

int index = 0;
foreach (var g in dynamicColumns?.Where(x => !string.IsNullOrWhiteSpace(x.Format) && new ExcelNumberFormat(x.Format).IsValid).GroupBy(x => x.Format))
{
foreach (var col in g)
col.FormatId = startUpCellXfs + index;

yield return g.First();
index++;
}
}

}
}
39 changes: 19 additions & 20 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationTo
{
await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken);
await CreateZipEntryAsync(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString, cancellationToken);
await GenerateStylesXmlAsync(cancellationToken);
}

private async Task CreateSheetXmlAsync(object value, string sheetPath, CancellationToken cancellationToken)
Expand All @@ -47,27 +48,27 @@ private async Task CreateSheetXmlAsync(object value, string sheetPath, Cancellat
if (value == null)
{
await WriteEmptySheetAsync(writer);
goto End; //for re-using code
}

//DapperRow

switch (value)
else
{
case IDataReader dataReader:
await GenerateSheetByIDataReaderAsync(writer, dataReader);
break;
case IEnumerable enumerable:
await GenerateSheetByEnumerableAsync(writer, enumerable);
break;
case DataTable dataTable:
await GenerateSheetByDataTableAsync(writer, dataTable);
break;
default:
throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue.");
//DapperRow

switch (value)
{
case IDataReader dataReader:
await GenerateSheetByIDataReaderAsync(writer, dataReader);
break;
case IEnumerable enumerable:
await GenerateSheetByEnumerableAsync(writer, enumerable);
break;
case DataTable dataTable:
await GenerateSheetByDataTableAsync(writer, dataTable);
break;
default:
throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue.");
}
}
}
End: //for re-using code
_zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet));
}

Expand Down Expand Up @@ -455,8 +456,6 @@ private async Task GenerateEndXmlAsync(CancellationToken cancellationToken)
{
await AddFilesToZipAsync(cancellationToken);

await GenerateStylesXmlAsync(cancellationToken);

await GenerateDrawinRelXmlAsync(cancellationToken);

await GenerateDrawingXmlAsync(cancellationToken);
Expand All @@ -479,7 +478,7 @@ private async Task AddFilesToZipAsync(CancellationToken cancellationToken)
/// </summary>
private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken)
{
var styleXml = GetStylesXml();
var styleXml = GetStylesXml(_configuration.DynamicColumns);

await CreateZipEntryAsync(
ExcelFileNames.Styles,
Expand Down
70 changes: 27 additions & 43 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using MiniExcelLibs.OpenXml.Constants;
using MiniExcelLibs.Attributes;
using MiniExcelLibs.OpenXml.Constants;
using MiniExcelLibs.OpenXml.Models;
using MiniExcelLibs.Utils;
using MiniExcelLibs.Zip;
Expand Down Expand Up @@ -108,6 +109,7 @@ private ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName
if (dynamicColumn.Format != null)
{
prop.ExcelFormat = dynamicColumn.Format;
prop.ExcelFormatId = dynamicColumn.FormatId;
}

if (dynamicColumn.Aliases != null)
Expand Down Expand Up @@ -158,14 +160,26 @@ private Tuple<string, string, string> GetCellValue(int rowIndex, int cellIndex,
return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(str));
}

if (columnInfo?.ExcelFormat != null && value is IFormattable formattableValue)
var type = GetValueType(value, columnInfo);


if (columnInfo?.ExcelFormat != null && columnInfo?.ExcelFormatId == -1 && value is IFormattable formattableValue)
{
var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture);
return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(formattedStr));
}

var type = GetValueType(value, columnInfo);
if (type == typeof(DateTime))
{
return GetDateTimeValue((DateTime)value, columnInfo);
}

#if NET6_0_OR_GREATER
if (type == typeof(DateOnly))
{
return GetDateTimeValue(((DateOnly)value).ToDateTime(new TimeOnly()), columnInfo);
}
#endif
if (type.IsEnum)
{
var description = CustomPropertyHelper.DescriptionAttr(type, value);
Expand Down Expand Up @@ -193,33 +207,6 @@ private Tuple<string, string, string> GetCellValue(int rowIndex, int cellIndex,
return Tuple.Create("4", "str", ExcelOpenXmlUtils.EncodeXML(base64));
}

if (type == typeof(DateTime))
{
return GetDateTimeValue(value, columnInfo);
}

#if NET6_0_OR_GREATER
if (type == typeof(DateOnly))
{
if (_configuration.Culture != CultureInfo.InvariantCulture)
{
var cellValue = ((DateOnly)value).ToString(_configuration.Culture);
return Tuple.Create("2", "str", cellValue);
}

if (columnInfo == null || columnInfo.ExcelFormat == null)
{
var oaDate = CorrectDateTimeValue((DateTime)value);
var cellValue = oaDate.ToString(CultureInfo.InvariantCulture);
return Tuple.Create<string, string, string>("3", null, cellValue);
}

// TODO: now it'll lose date type information
var formattedCellValue = ((DateOnly)value).ToString(columnInfo.ExcelFormat, _configuration.Culture);
return Tuple.Create("2", "str", formattedCellValue);
}
#endif

return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(value.ToString()));
}

Expand Down Expand Up @@ -325,24 +312,21 @@ private string GetFileValue(int rowIndex, int cellIndex, object value)
return base64;
}

private Tuple<string, string, string> GetDateTimeValue(object value, ExcelColumnInfo columnInfo)
private Tuple<string, string, string> GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo)
{
string cellValue = null;
if (_configuration.Culture != CultureInfo.InvariantCulture)
{
var cellValue = ((DateTime)value).ToString(_configuration.Culture);
cellValue = (value).ToString(_configuration.Culture);
return Tuple.Create("2", "str", cellValue);
}

var oaDate = CorrectDateTimeValue(value);
cellValue = oaDate.ToString(CultureInfo.InvariantCulture);
if (columnInfo == null || columnInfo.ExcelFormat == null)
{
var oaDate = CorrectDateTimeValue((DateTime)value);
var cellValue = oaDate.ToString(CultureInfo.InvariantCulture);
return Tuple.Create<string, string, string>("3", null, cellValue);
}

// TODO: now it'll lose date type information
var formattedCellValue = ((DateTime)value).ToString(columnInfo.ExcelFormat, _configuration.Culture);
return Tuple.Create("2", "str", formattedCellValue);
else
return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue);
}

private static double CorrectDateTimeValue(DateTime value)
Expand Down Expand Up @@ -375,14 +359,14 @@ private string GetDimensionRef(int maxRowIndex, int maxColumnIndex)
return dimensionRef;
}

private string GetStylesXml()
private string GetStylesXml(ICollection<ExcelColumnAttribute> columns)
{
switch (_configuration.TableStyles)
{
case TableStyles.None:
return ExcelXml.NoneStylesXml;
return ExcelXml.SetupStyleXml(ExcelXml.NoneStylesXml, columns);
case TableStyles.Default:
return ExcelXml.DefaultStylesXml;
return ExcelXml.SetupStyleXml(ExcelXml.DefaultStylesXml, columns);
default:
return string.Empty;
}
Expand Down
Loading
Loading