CSV enumeration and code reusage (#600)

* 1900 year DateTime correction

* Rewrite CSV enumeration

* Maximize code reusage

* Reuse OADate correction

* Update to latest version of CodeQL

* Update CodeQL to v3

---------

Co-authored-by: Lukasz Arciszewski <lukasz.arciszewski@accenture.com>
This commit is contained in:
duszekmestre 2024-05-19 08:29:15 +02:00 committed by GitHub
parent 49bc8e99ed
commit fdfe883904
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 556 additions and 480 deletions

16
.editorconfig Normal file
View File

@ -0,0 +1,16 @@
[*.{cs,vb}]
# IDE0009: Member access should be qualified.
dotnet_style_qualification_for_event = false
# IDE0009: Member access should be qualified.
dotnet_style_qualification_for_field = false
# IDE0009: Member access should be qualified.
dotnet_style_qualification_for_property = false
# IDE0009: Member access should be qualified.
dotnet_style_qualification_for_method = false
# IDE0065: Misplaced using directive
csharp_using_directive_placement = outside_namespace

View File

@ -42,7 +42,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -53,7 +53,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -67,4 +67,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v3

View File

@ -42,100 +42,94 @@ namespace MiniExcelLibs.Csv
} }
var type = _value.GetType(); var type = _value.GetType();
Type genericType = null;
if (_value is IDataReader) if (_value is IDataReader dataReader)
{ {
GenerateSheetByIDataReader(_value, seperator, newLine, _writer); GenerateSheetByIDataReader(dataReader, seperator, newLine, _writer);
} }
else if (_value is IEnumerable) else if (_value is IEnumerable enumerable)
{ {
var values = _value as IEnumerable; GenerateSheetByIEnumerable(enumerable, seperator, newLine, _writer);
List<object> keys = new List<object>();
List<ExcelColumnInfo> props = null;
string mode = null;
// check mode
{
foreach (var item in values) //TODO: need to optimize
{
if (item != null && mode == null)
{
if (item is IDictionary<string, object>)
{
var item2 = item as IDictionary<string, object>;
mode = "IDictionary<string, object>";
foreach (var key in item2.Keys)
keys.Add(key);
}
else if (item is IDictionary)
{
var item2 = item as IDictionary;
mode = "IDictionary";
foreach (var key in item2.Keys)
keys.Add(key);
}
else
{
mode = "Properties";
genericType = item.GetType();
props = CustomPropertyHelper.GetSaveAsProperties(genericType, _configuration);
}
break;
}
}
}
//if(mode == null)
// throw new NotImplementedException($"Type {type?.Name} & genericType {genericType?.Name} not Implemented. please issue for me.");
if (keys.Count == 0 && props == null)
{
_writer.Write(newLine);
this._writer.Flush();
return;
}
if (this._printHeader)
{
if (props != null)
{
_writer.Write(string.Join(seperator, props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration.AlwaysQuote, _configuration.Seperator))));
_writer.Write(newLine);
}
else if (keys.Count > 0)
{
_writer.Write(string.Join(seperator, keys.Select(s => CsvHelpers.ConvertToCsvValue(s.ToString(), _configuration.AlwaysQuote, _configuration.Seperator))));
_writer.Write(newLine);
}
else
{
throw new InvalidOperationException("Please issue for me.");
}
}
if (mode == "IDictionary<string, object>") //Dapper Row
GenerateSheetByDapperRow(_writer, _value as IEnumerable, keys.Cast<string>().ToList(), seperator, newLine);
else if (mode == "IDictionary") //IDictionary
GenerateSheetByIDictionary(_writer, _value as IEnumerable, keys, seperator, newLine);
else if (mode == "Properties")
GenerateSheetByProperties(_writer, _value as IEnumerable, props, seperator, newLine);
else
throw new NotImplementedException($"Type {type?.Name} & genericType {genericType?.Name} not Implemented. please issue for me.");
} }
else if (_value is DataTable) else if (_value is DataTable dataTable)
{ {
GenerateSheetByDataTable(_writer, _value as DataTable, seperator, newLine); GenerateSheetByDataTable(_writer, dataTable, seperator, newLine);
} }
else else
{ {
throw new NotImplementedException($"Type {type?.Name} & genericType {genericType?.Name} not Implemented. please issue for me."); throw new NotImplementedException($"Type {type?.Name} not Implemented. please issue for me.");
} }
this._writer.Flush(); this._writer.Flush();
} }
} }
private void GenerateSheetByIEnumerable(IEnumerable values, string seperator, string newLine, StreamWriter writer)
{
Type genericType = null;
List<ExcelColumnInfo> props = null;
string mode = null;
var enumerator = values.GetEnumerator();
var empty = !enumerator.MoveNext();
if (empty)
{
// only when empty IEnumerable need to check this issue #133 https://github.com/shps951023/MiniExcel/issues/133
genericType = TypeHelper.GetGenericIEnumerables(values).FirstOrDefault();
if (genericType == null || genericType == typeof(object) // sometime generic type will be object, e.g: https://user-images.githubusercontent.com/12729184/132812859-52984314-44d1-4ee8-9487-2d1da159f1f0.png
|| typeof(IDictionary<string, object>).IsAssignableFrom(genericType)
|| typeof(IDictionary).IsAssignableFrom(genericType)
|| typeof(KeyValuePair<string, object>).IsAssignableFrom(genericType))
{
_writer.Write(newLine);
this._writer.Flush();
return;
}
mode = "Properties";
props = CustomPropertyHelper.GetSaveAsProperties(genericType, _configuration);
}
else
{
var firstItem = enumerator.Current;
if (firstItem is IDictionary<string, object> genericDic)
{
mode = "IDictionary<string, object>";
props = CustomPropertyHelper.GetDictionaryColumnInfo(genericDic, null, _configuration);
}
else if (firstItem is IDictionary dic)
{
mode = "IDictionary";
props = CustomPropertyHelper.GetDictionaryColumnInfo(null, dic, _configuration);
mode = "IDictionary";
}
else
{
mode = "Properties";
genericType = firstItem.GetType();
props = CustomPropertyHelper.GetSaveAsProperties(genericType, _configuration);
}
}
if (this._printHeader)
{
_writer.Write(string.Join(seperator, props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration.AlwaysQuote, _configuration.Seperator))));
_writer.Write(newLine);
}
if (!empty)
{
if (mode == "IDictionary<string, object>") //Dapper Row
GenerateSheetByDapperRow(_writer, enumerator, props.Select(x => x.Key.ToString()).ToList(), seperator, newLine);
else if (mode == "IDictionary") //IDictionary
GenerateSheetByIDictionary(_writer, enumerator, props.Select(x => x.Key).ToList(), seperator, newLine);
else if (mode == "Properties")
GenerateSheetByProperties(_writer, enumerator, props, seperator, newLine);
else
throw new NotImplementedException($"Mode for genericType {genericType?.Name} not Implemented. please issue for me.");
}
}
public void Insert() public void Insert()
{ {
SaveAs(); SaveAs();
@ -202,34 +196,37 @@ namespace MiniExcelLibs.Csv
} }
} }
private void GenerateSheetByProperties(StreamWriter writer, IEnumerable value, List<ExcelColumnInfo> props, string seperator, string newLine) private void GenerateSheetByProperties(StreamWriter writer, IEnumerator value, List<ExcelColumnInfo> props, string seperator, string newLine)
{ {
foreach (var v in value) do
{ {
var v = value.Current;
var values = props.Select(s => CsvHelpers.ConvertToCsvValue(ToCsvString(s?.Property.GetValue(v), s), _configuration.AlwaysQuote, _configuration.Seperator)); var values = props.Select(s => CsvHelpers.ConvertToCsvValue(ToCsvString(s?.Property.GetValue(v), s), _configuration.AlwaysQuote, _configuration.Seperator));
writer.Write(string.Join(seperator, values)); writer.Write(string.Join(seperator, values));
writer.Write(newLine); writer.Write(newLine);
} } while (value.MoveNext());
} }
private void GenerateSheetByIDictionary(StreamWriter writer, IEnumerable value, List<object> keys, string seperator, string newLine) private void GenerateSheetByIDictionary(StreamWriter writer, IEnumerator value, List<object> keys, string seperator, string newLine)
{ {
foreach (IDictionary v in value) do
{ {
var v = (IDictionary)value.Current;
var values = keys.Select(key => CsvHelpers.ConvertToCsvValue(ToCsvString(v[key], null), _configuration.AlwaysQuote, _configuration.Seperator)); var values = keys.Select(key => CsvHelpers.ConvertToCsvValue(ToCsvString(v[key], null), _configuration.AlwaysQuote, _configuration.Seperator));
writer.Write(string.Join(seperator, values)); writer.Write(string.Join(seperator, values));
writer.Write(newLine); writer.Write(newLine);
} } while (value.MoveNext());
} }
private void GenerateSheetByDapperRow(StreamWriter writer, IEnumerable value, List<string> keys, string seperator, string newLine) private void GenerateSheetByDapperRow(StreamWriter writer, IEnumerator value, List<string> keys, string seperator, string newLine)
{ {
foreach (IDictionary<string, object> v in value) do
{ {
var v = (IDictionary<string, object>)value.Current;
var values = keys.Select(key => CsvHelpers.ConvertToCsvValue(ToCsvString(v[key], null), _configuration.AlwaysQuote, _configuration.Seperator)); var values = keys.Select(key => CsvHelpers.ConvertToCsvValue(ToCsvString(v[key], null), _configuration.AlwaysQuote, _configuration.Seperator));
writer.Write(string.Join(seperator, values)); writer.Write(string.Join(seperator, values));
writer.Write(newLine); writer.Write(newLine);
} } while (value.MoveNext());
} }
public string ToCsvString(object value, ExcelColumnInfo p) public string ToCsvString(object value, ExcelColumnInfo p)
@ -283,5 +280,4 @@ namespace MiniExcelLibs.Csv
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }
}
}

View File

@ -21,43 +21,43 @@ namespace MiniExcelLibs.OpenXml
switch (_value) switch (_value)
{ {
case IDictionary<string, object> sheets: case IDictionary<string, object> sheets:
{
var sheetId = 0;
_sheets.RemoveAt(0);//TODO:remove
foreach (var sheet in sheets)
{ {
var sheetId = 0; sheetId++;
_sheets.RemoveAt(0);//TODO:remove var sheetInfos = GetSheetInfos(sheet.Key);
foreach (var sheet in sheets) var sheetDto = sheetInfos.ToDto(sheetId);
{ _sheets.Add(sheetDto); //TODO:remove
sheetId++;
var sheetInfos = GetSheetInfos(sheet.Key);
var sheetDto = sheetInfos.ToDto(sheetId);
_sheets.Add(sheetDto); //TODO:remove
currentSheetIndex = sheetId; currentSheetIndex = sheetId;
await CreateSheetXmlAsync(sheet.Value, sheetDto.Path, cancellationToken); await CreateSheetXmlAsync(sheet.Value, sheetDto.Path, cancellationToken);
}
break;
} }
break;
}
case DataSet sheets: case DataSet sheets:
{
var sheetId = 0;
_sheets.RemoveAt(0);//TODO:remove
foreach (DataTable dt in sheets.Tables)
{ {
var sheetId = 0; sheetId++;
_sheets.RemoveAt(0);//TODO:remove var sheetInfos = GetSheetInfos(dt.TableName);
foreach (DataTable dt in sheets.Tables) var sheetDto = sheetInfos.ToDto(sheetId);
{ _sheets.Add(sheetDto); //TODO:remove
sheetId++;
var sheetInfos = GetSheetInfos(dt.TableName);
var sheetDto = sheetInfos.ToDto(sheetId);
_sheets.Add(sheetDto); //TODO:remove
currentSheetIndex = sheetId; currentSheetIndex = sheetId;
await CreateSheetXmlAsync(dt, sheetDto.Path, cancellationToken); await CreateSheetXmlAsync(dt, sheetDto.Path, cancellationToken);
}
break;
} }
break;
}
default: default:
//Single sheet export. //Single sheet export.
currentSheetIndex++; currentSheetIndex++;
@ -72,25 +72,8 @@ namespace MiniExcelLibs.OpenXml
internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken) internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken)
{ {
await CreateZipEntryAsync("_rels/.rels", "application/vnd.openxmlformats-package.relationships+xml", ExcelOpenXmlSheetWriter._defaultRels, cancellationToken); await CreateZipEntryAsync("_rels/.rels", "application/vnd.openxmlformats-package.relationships+xml", _defaultRels, cancellationToken);
await CreateZipEntryAsync("xl/sharedStrings.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", ExcelOpenXmlSheetWriter._defaultSharedString, cancellationToken); await CreateZipEntryAsync("xl/sharedStrings.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", _defaultSharedString, cancellationToken);
}
private async Task CreateZipEntryAsync(string path, string contentType, string content, CancellationToken cancellationToken)
{
ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest);
using (var zipStream = entry.Open())
using (MiniExcelAsyncStreamWriter writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken))
await writer.WriteAsync(content);
if (!string.IsNullOrEmpty(contentType))
_zipDictionary.Add(path, new ZipPackageInfo(entry, contentType));
}
private async Task CreateZipEntryAsync(string path, byte[] content, CancellationToken cancellationToken)
{
ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest);
using (var zipStream = entry.Open())
await zipStream.WriteAsync(content, 0, content.Length, cancellationToken);
} }
private async Task CreateSheetXmlAsync(object value, string sheetPath, CancellationToken cancellationToken) private async Task CreateSheetXmlAsync(object value, string sheetPath, CancellationToken cancellationToken)
@ -244,13 +227,13 @@ namespace MiniExcelLibs.OpenXml
if (firstItem is IDictionary<string, object> genericDic) if (firstItem is IDictionary<string, object> genericDic)
{ {
mode = "IDictionary<string, object>"; mode = "IDictionary<string, object>";
props = GetDictionaryColumnInfo(genericDic, null); props = CustomPropertyHelper.GetDictionaryColumnInfo(genericDic, null, _configuration);
maxColumnIndex = props.Count; maxColumnIndex = props.Count;
} }
else if (firstItem is IDictionary dic) else if (firstItem is IDictionary dic)
{ {
mode = "IDictionary"; mode = "IDictionary";
props = GetDictionaryColumnInfo(null, dic); props = CustomPropertyHelper.GetDictionaryColumnInfo(null, dic, _configuration);
//maxColumnIndex = dic.Keys.Count; //maxColumnIndex = dic.Keys.Count;
maxColumnIndex = props.Count; // why not using keys, because ignore attribute ![image](https://user-images.githubusercontent.com/12729184/163686902-286abb70-877b-4e84-bd3b-001ad339a84a.png) maxColumnIndex = props.Count; // why not using keys, because ignore attribute ![image](https://user-images.githubusercontent.com/12729184/163686902-286abb70-877b-4e84-bd3b-001ad339a84a.png)
} }
@ -416,7 +399,7 @@ namespace MiniExcelLibs.OpenXml
} }
var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex); var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex);
WriteCAsync(writer, r, columnName: p.ExcelColumnName); await WriteCAsync(writer, r, columnName: p.ExcelColumnName);
xIndex++; xIndex++;
} }
@ -450,10 +433,14 @@ namespace MiniExcelLibs.OpenXml
var v = tuple.Item3; var v = tuple.Item3;
if (v != null && (v.StartsWith(" ", StringComparison.Ordinal) || v.EndsWith(" ", StringComparison.Ordinal))) /*Prefix and suffix blank space will lost after SaveAs #294*/ if (v != null && (v.StartsWith(" ", StringComparison.Ordinal) || v.EndsWith(" ", StringComparison.Ordinal))) /*Prefix and suffix blank space will lost after SaveAs #294*/
{
await writer.WriteAsync($"<x:c r=\"{columname}\" {(t == null ? "" : $"t =\"{t}\"")} s=\"{s}\" xml:space=\"preserve\"><x:v>{v}</x:v></x:c>"); await writer.WriteAsync($"<x:c r=\"{columname}\" {(t == null ? "" : $"t =\"{t}\"")} s=\"{s}\" xml:space=\"preserve\"><x:v>{v}</x:v></x:c>");
}
else else
//t check avoid format error ![image](https://user-images.githubusercontent.com/12729184/118770190-9eee3480-b8b3-11eb-9f5a-87a439f5e320.png) {
//to check avoid format error ![image](https://user-images.githubusercontent.com/12729184/118770190-9eee3480-b8b3-11eb-9f5a-87a439f5e320.png)
await writer.WriteAsync($"<x:c r=\"{columname}\" {(t == null ? "" : $"t =\"{t}\"")} s=\"{s}\"><x:v>{v}</x:v></x:c>"); await writer.WriteAsync($"<x:c r=\"{columname}\" {(t == null ? "" : $"t =\"{t}\"")} s=\"{s}\"><x:v>{v}</x:v></x:c>");
}
} }
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, int xIndex = 1, int yIndex = 1)
@ -474,6 +461,7 @@ namespace MiniExcelLibs.OpenXml
cellIndex++; cellIndex++;
continue; continue;
} }
object cellValue = null; object cellValue = null;
if (isDic) if (isDic)
{ {
@ -489,11 +477,12 @@ namespace MiniExcelLibs.OpenXml
{ {
cellValue = p.Property.GetValue(v); cellValue = p.Property.GetValue(v);
} }
await WriteCellAsync(writer, yIndex, cellIndex, cellValue, p);
await WriteCellAsync(writer, yIndex, cellIndex, cellValue, p);
cellIndex++; cellIndex++;
} }
await writer.WriteAsync($"</x:row>"); await writer.WriteAsync($"</x:row>");
yIndex++; yIndex++;
} while (value.MoveNext()); } while (value.MoveNext());
@ -503,132 +492,121 @@ namespace MiniExcelLibs.OpenXml
private async Task GenerateEndXmlAsync(CancellationToken cancellationToken) private async Task GenerateEndXmlAsync(CancellationToken cancellationToken)
{ {
//Files await AddFilesToZipAsync(cancellationToken);
await GenerateStylesXmlAsync(cancellationToken);
await GenerateDrawinRelXmlAsync(cancellationToken);
await GenerateDrawingXmlAsync(cancellationToken);
await GenerateWorkbookXmlAsync(cancellationToken);
await GenerateContentTypesXmlAsync(cancellationToken);
}
private async Task AddFilesToZipAsync(CancellationToken cancellationToken)
{
foreach (var item in _files)
{ {
foreach (var item in _files) await this.CreateZipEntryAsync(item.Path, item.Byte, cancellationToken);
{
await this.CreateZipEntryAsync(item.Path, item.Byte, cancellationToken);
}
}
// styles.xml
{
var styleXml = string.Empty;
if (_configuration.TableStyles == TableStyles.None)
{
styleXml = _noneStylesXml;
}
else if (_configuration.TableStyles == TableStyles.Default)
{
styleXml = _defaultStylesXml;
}
await CreateZipEntryAsync(@"xl/styles.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", styleXml, cancellationToken);
}
// drawing rel
{
for (int j = 0; j < _sheets.Count; j++)
{
var drawing = new StringBuilder();
foreach (var i in _files.Where(w => w.IsImage && w.SheetId == j + 1))
{
drawing.AppendLine($@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"" Target=""{i.Path2}"" Id=""{i.ID}"" />");
}
await CreateZipEntryAsync($"xl/drawings/_rels/drawing{j + 1}.xml.rels", "",
_defaultDrawingXmlRels.Replace("{{format}}", drawing.ToString()), cancellationToken);
}
}
// drawing
{
for (int j = 0; j < _sheets.Count; j++)
{
var drawing = new StringBuilder();
foreach (var i in _files.Where(w => w.IsImage && w.SheetId == j + 1))
{
drawing.Append($@"<xdr:oneCellAnchor>
<xdr:from>
<xdr:col>{i.CellIndex - 1/* why -1 : https://user-images.githubusercontent.com/12729184/150460189-f08ed939-44d4-44e1-be6e-9c533ece6be8.png*/}</xdr:col>
<xdr:colOff>0</xdr:colOff>
<xdr:row>{i.RowIndex - 1}</xdr:row>
<xdr:rowOff>0</xdr:rowOff>
</xdr:from>
<xdr:ext cx=""609600"" cy=""190500"" />
<xdr:pic>
<xdr:nvPicPr>
<xdr:cNvPr id=""{_files.IndexOf(i) + 1}"" descr="""" name=""2a3f9147-58ea-4a79-87da-7d6114c4877b"" />
<xdr:cNvPicPr>
<a:picLocks noChangeAspect=""1"" />
</xdr:cNvPicPr>
</xdr:nvPicPr>
<xdr:blipFill>
<a:blip r:embed=""{i.ID}"" cstate=""print"" />
<a:stretch>
<a:fillRect />
</a:stretch>
</xdr:blipFill>
<xdr:spPr>
<a:xfrm>
<a:off x=""0"" y=""0"" />
<a:ext cx=""0"" cy=""0"" />
</a:xfrm>
<a:prstGeom prst=""rect"">
<a:avLst />
</a:prstGeom>
</xdr:spPr>
</xdr:pic>
<xdr:clientData />
</xdr:oneCellAnchor>");
}
await CreateZipEntryAsync($"xl/drawings/drawing{j + 1}.xml", "application/vnd.openxmlformats-officedocument.drawing+xml",
_defaultDrawing.Replace("{{format}}", drawing.ToString()), cancellationToken);
}
}
// workbook.xml 、 workbookRelsXml
{
var workbookXml = new StringBuilder();
var workbookRelsXml = new StringBuilder();
var sheetId = 0;
foreach (var s in _sheets)
{
sheetId++;
if (string.IsNullOrEmpty(s.State))
{
workbookXml.AppendLine($@"<x:sheet name=""{s.Name}"" sheetId=""{sheetId}"" r:id=""{s.ID}"" />");
}
else
{
workbookXml.AppendLine($@"<x:sheet name=""{s.Name}"" sheetId=""{sheetId}"" state=""{s.State}"" r:id=""{s.ID}"" />");
}
workbookRelsXml.AppendLine($@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"" Target=""/{s.Path}"" Id=""{s.ID}"" />");
//TODO: support multiple drawing
//TODO: ../drawings/drawing1.xml or /xl/drawings/drawing1.xml
var sheetRelsXml = $@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"" Target=""../drawings/drawing{sheetId}.xml"" Id=""drawing{sheetId}"" />";
await CreateZipEntryAsync($"xl/worksheets/_rels/sheet{s.SheetIdx}.xml.rels", "",
_defaultSheetRelXml.Replace("{{format}}", sheetRelsXml), cancellationToken);
}
await CreateZipEntryAsync(@"xl/workbook.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
_defaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), cancellationToken);
await CreateZipEntryAsync(@"xl/_rels/workbook.xml.rels", "",
_defaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken);
}
//[Content_Types].xml
{
var sb = new StringBuilder(@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><Types xmlns=""http://schemas.openxmlformats.org/package/2006/content-types""><Default ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"" Extension=""bin""/><Default ContentType=""application/xml"" Extension=""xml""/><Default ContentType=""image/jpeg"" Extension=""jpg""/><Default ContentType=""image/png"" Extension=""png""/><Default ContentType=""image/gif"" Extension=""gif""/><Default ContentType=""application/vnd.openxmlformats-package.relationships+xml"" Extension=""rels""/>");
foreach (var p in _zipDictionary)
sb.Append($"<Override ContentType=\"{p.Value.ContentType}\" PartName=\"/{p.Key}\" />");
sb.Append("</Types>");
ZipArchiveEntry entry = _archive.CreateEntry("[Content_Types].xml", CompressionLevel.Fastest);
using (var zipStream = entry.Open())
using (MiniExcelStreamWriter writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize))
writer.Write(sb.ToString());
} }
} }
/// <summary>
/// styles.xml
/// </summary>
private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken)
{
var styleXml = GetStylesXml();
await CreateZipEntryAsync(
@"xl/styles.xml",
"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
styleXml,
cancellationToken);
}
private async Task GenerateDrawinRelXmlAsync(CancellationToken cancellationToken)
{
for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++)
{
var drawing = GetDrawingRelationshipXml(sheetIndex);
await CreateZipEntryAsync($"xl/drawings/_rels/drawing{sheetIndex + 1}.xml.rels", "",
_defaultDrawingXmlRels.Replace("{{format}}", drawing), cancellationToken);
}
}
private async Task GenerateDrawingXmlAsync(CancellationToken cancellationToken)
{
for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++)
{
var drawing = GetDrawingXml(sheetIndex);
await CreateZipEntryAsync(
$"xl/drawings/drawing{sheetIndex + 1}.xml",
"application/vnd.openxmlformats-officedocument.drawing+xml",
_defaultDrawing.Replace("{{format}}", drawing),
cancellationToken);
}
}
/// <summary>
/// workbook.xml 、 workbookRelsXml
/// </summary>
private async Task GenerateWorkbookXmlAsync(CancellationToken cancellationToken)
{
GenerateWorkBookXmls(
out StringBuilder workbookXml,
out StringBuilder workbookRelsXml,
out Dictionary<int, string> sheetsRelsXml);
foreach (var sheetRelsXml in sheetsRelsXml)
{
await CreateZipEntryAsync(
$"xl/worksheets/_rels/sheet{sheetRelsXml.Key}.xml.rels",
null,
_defaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value),
cancellationToken);
}
await CreateZipEntryAsync(
@"xl/workbook.xml",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
_defaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()),
cancellationToken);
await CreateZipEntryAsync(
@"xl/_rels/workbook.xml.rels",
null,
_defaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()),
cancellationToken);
}
/// <summary>
/// [Content_Types].xml
/// </summary>
private async Task GenerateContentTypesXmlAsync(CancellationToken cancellationToken)
{
var contentTypes = GetContentTypesXml();
await CreateZipEntryAsync(@"[Content_Types].xml", null, contentTypes, cancellationToken);
}
private async Task CreateZipEntryAsync(string path, string contentType, string content, CancellationToken cancellationToken)
{
ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest);
using (var zipStream = entry.Open())
using (MiniExcelAsyncStreamWriter writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken))
await writer.WriteAsync(content);
if (!string.IsNullOrEmpty(contentType))
_zipDictionary.Add(path, new ZipPackageInfo(entry, contentType));
}
private async Task CreateZipEntryAsync(string path, byte[] content, CancellationToken cancellationToken)
{
ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest);
using (var zipStream = entry.Open())
await zipStream.WriteAsync(content, 0, content.Length, cancellationToken);
}
} }
} }

View File

@ -1,6 +1,7 @@
using MiniExcelLibs.Zip; using MiniExcelLibs.Zip;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Compression; using System.Linq;
using System.Text;
namespace MiniExcelLibs.OpenXml namespace MiniExcelLibs.OpenXml
{ {
@ -46,8 +47,8 @@ namespace MiniExcelLibs.OpenXml
</x:cellStyleXfs> </x:cellStyleXfs>
<x:cellXfs> <x:cellXfs>
<x:xf /> <x:xf />
<x:xf /> <x:xf />
<x:xf /> <x:xf />
<x:xf numFmtId=""14"" applyNumberFormat=""1"" /> <x:xf numFmtId=""14"" applyNumberFormat=""1"" />
</x:cellXfs> </x:cellXfs>
</x:styleSheet>"; </x:styleSheet>";
@ -182,27 +183,113 @@ namespace MiniExcelLibs.OpenXml
private static readonly string _defaultSharedString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><sst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" count=\"0\" uniqueCount=\"0\"></sst>"; private static readonly string _defaultSharedString = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><sst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" count=\"0\" uniqueCount=\"0\"></sst>";
private static string MinifyXml(string xml) => xml.Replace("\r", "").Replace("\n", "").Replace("\t", ""); private static string MinifyXml(string xml) => xml.Replace("\r", "").Replace("\n", "").Replace("\t", "");
internal void GenerateDefaultOpenXml() private string GetStylesXml()
{ {
CreateZipEntry("_rels/.rels", "application/vnd.openxmlformats-package.relationships+xml", ExcelOpenXmlSheetWriter._defaultRels); var styleXml = string.Empty;
CreateZipEntry("xl/sharedStrings.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", ExcelOpenXmlSheetWriter._defaultSharedString);
if (_configuration.TableStyles == TableStyles.None)
{
styleXml = _noneStylesXml;
}
else if (_configuration.TableStyles == TableStyles.Default)
{
styleXml = _defaultStylesXml;
}
return styleXml;
} }
private void CreateZipEntry(string path, string contentType, string content) private string GetDrawingRelationshipXml(int sheetIndex)
{ {
ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); var drawing = new StringBuilder();
using (var zipStream = entry.Open()) foreach (var i in _files.Where(w => w.IsImage && w.SheetId == sheetIndex + 1))
using (MiniExcelStreamWriter writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) {
writer.Write(content); drawing.AppendLine($@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"" Target=""{i.Path2}"" Id=""{i.ID}"" />");
if (!string.IsNullOrEmpty(contentType)) }
_zipDictionary.Add(path, new ZipPackageInfo(entry, contentType));
return drawing.ToString();
} }
private void CreateZipEntry(string path, byte[] content) private string GetDrawingXml(int sheetIndex)
{ {
ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest); var drawing = new StringBuilder();
using (var zipStream = entry.Open()) foreach (var file in _files.Where(w => w.IsImage && w.SheetId == sheetIndex + 1))
zipStream.Write(content, 0, content.Length); {
drawing.Append($@"<xdr:oneCellAnchor>
<xdr:from>
<xdr:col>{file.CellIndex - 1/* why -1 : https://user-images.githubusercontent.com/12729184/150460189-f08ed939-44d4-44e1-be6e-9c533ece6be8.png*/}</xdr:col>
<xdr:colOff>0</xdr:colOff>
<xdr:row>{file.RowIndex - 1}</xdr:row>
<xdr:rowOff>0</xdr:rowOff>
</xdr:from>
<xdr:ext cx=""609600"" cy=""190500"" />
<xdr:pic>
<xdr:nvPicPr>
<xdr:cNvPr id=""{_files.IndexOf(file) + 1}"" descr="""" name=""2a3f9147-58ea-4a79-87da-7d6114c4877b"" />
<xdr:cNvPicPr>
<a:picLocks noChangeAspect=""1"" />
</xdr:cNvPicPr>
</xdr:nvPicPr>
<xdr:blipFill>
<a:blip r:embed=""{file.ID}"" cstate=""print"" />
<a:stretch>
<a:fillRect />
</a:stretch>
</xdr:blipFill>
<xdr:spPr>
<a:xfrm>
<a:off x=""0"" y=""0"" />
<a:ext cx=""0"" cy=""0"" />
</a:xfrm>
<a:prstGeom prst=""rect"">
<a:avLst />
</a:prstGeom>
</xdr:spPr>
</xdr:pic>
<xdr:clientData />
</xdr:oneCellAnchor>");
}
return drawing.ToString();
}
private void GenerateWorkBookXmls(
out StringBuilder workbookXml,
out StringBuilder workbookRelsXml,
out Dictionary<int, string> sheetsRelsXml)
{
workbookXml = new StringBuilder();
workbookRelsXml = new StringBuilder();
sheetsRelsXml = new Dictionary<int, string>();
var sheetId = 0;
foreach (var s in _sheets)
{
sheetId++;
if (string.IsNullOrEmpty(s.State))
{
workbookXml.AppendLine($@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(s.Name)}"" sheetId=""{sheetId}"" r:id=""{s.ID}"" />");
}
else
{
workbookXml.AppendLine($@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(s.Name)}"" sheetId=""{sheetId}"" state=""{s.State}"" r:id=""{s.ID}"" />");
}
workbookRelsXml.AppendLine($@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"" Target=""/{s.Path}"" Id=""{s.ID}"" />");
//TODO: support multiple drawing
//TODO: ../drawings/drawing1.xml or /xl/drawings/drawing1.xml
var sheetRelsXml = $@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"" Target=""../drawings/drawing{sheetId}.xml"" Id=""drawing{sheetId}"" />";
sheetsRelsXml.Add(s.SheetIdx, sheetRelsXml);
}
}
private string GetContentTypesXml()
{
var sb = new StringBuilder(@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><Types xmlns=""http://schemas.openxmlformats.org/package/2006/content-types""><Default ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"" Extension=""bin""/><Default ContentType=""application/xml"" Extension=""xml""/><Default ContentType=""image/jpeg"" Extension=""jpg""/><Default ContentType=""image/png"" Extension=""png""/><Default ContentType=""image/gif"" Extension=""gif""/><Default ContentType=""application/vnd.openxmlformats-package.relationships+xml"" Extension=""rels""/>");
foreach (var p in _zipDictionary)
sb.Append($"<Override ContentType=\"{p.Value.ContentType}\" PartName=\"/{p.Key}\" />");
sb.Append("</Types>");
return sb.ToString();
} }
} }
} }

View File

@ -9,6 +9,7 @@ using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using static MiniExcelLibs.Utils.ImageHelper; using static MiniExcelLibs.Utils.ImageHelper;
namespace MiniExcelLibs.OpenXml namespace MiniExcelLibs.OpenXml
@ -120,6 +121,12 @@ namespace MiniExcelLibs.OpenXml
_archive.Dispose(); _archive.Dispose();
} }
internal void GenerateDefaultOpenXml()
{
CreateZipEntry("_rels/.rels", "application/vnd.openxmlformats-package.relationships+xml", _defaultRels);
CreateZipEntry("xl/sharedStrings.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", _defaultSharedString);
}
private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable values) private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable values)
{ {
var maxColumnIndex = 0; var maxColumnIndex = 0;
@ -168,13 +175,13 @@ namespace MiniExcelLibs.OpenXml
if (firstItem is IDictionary<string, object> genericDic) if (firstItem is IDictionary<string, object> genericDic)
{ {
mode = "IDictionary<string, object>"; mode = "IDictionary<string, object>";
props = GetDictionaryColumnInfo(genericDic, null); props = CustomPropertyHelper.GetDictionaryColumnInfo(genericDic, null, _configuration);
maxColumnIndex = props.Count; maxColumnIndex = props.Count;
} }
else if (firstItem is IDictionary dic) else if (firstItem is IDictionary dic)
{ {
mode = "IDictionary"; mode = "IDictionary";
props = GetDictionaryColumnInfo(null, dic); props = CustomPropertyHelper.GetDictionaryColumnInfo(null, dic, _configuration);
//maxColumnIndex = dic.Keys.Count; //maxColumnIndex = dic.Keys.Count;
maxColumnIndex = props.Count; // why not using keys, because ignore attribute ![image](https://user-images.githubusercontent.com/12729184/163686902-286abb70-877b-4e84-bd3b-001ad339a84a.png) maxColumnIndex = props.Count; // why not using keys, because ignore attribute ![image](https://user-images.githubusercontent.com/12729184/163686902-286abb70-877b-4e84-bd3b-001ad339a84a.png)
} }
@ -303,55 +310,6 @@ namespace MiniExcelLibs.OpenXml
_zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml")); _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"));
} }
private List<ExcelColumnInfo> GetDictionaryColumnInfo(IDictionary<string, object> dicString, IDictionary dic)
{
List<ExcelColumnInfo> props;
var _props = new List<ExcelColumnInfo>();
if (dicString != null)
foreach (var key in dicString.Keys)
SetDictionaryColumnInfo(_props, key);
else if (dic != null)
foreach (var key in dic.Keys)
SetDictionaryColumnInfo(_props, key);
else
throw new NotSupportedException("SetDictionaryColumnInfo Error");
props = CustomPropertyHelper.SortCustomProps(_props);
return props;
}
private void SetDictionaryColumnInfo(List<ExcelColumnInfo> _props, object key)
{
var p = new ExcelColumnInfo();
p.ExcelColumnName = key?.ToString();
p.Key = key;
// TODO:Dictionary value type is not fiexed
//var _t =
//var gt = Nullable.GetUnderlyingType(p.PropertyType);
var isIgnore = false;
if (_configuration.DynamicColumns != null && _configuration.DynamicColumns.Length > 0)
{
var dynamicColumn = _configuration.DynamicColumns.SingleOrDefault(_ => _.Key == key.ToString());
if (dynamicColumn != null)
{
p.Nullable = true;
//p.ExcludeNullableType = item2[key]?.GetType();
if (dynamicColumn.Format != null)
p.ExcelFormat = dynamicColumn.Format;
if (dynamicColumn.Aliases != null)
p.ExcelColumnAliases = dynamicColumn.Aliases;
if (dynamicColumn.IndexName != null)
p.ExcelIndexName = dynamicColumn.IndexName;
p.ExcelColumnIndex = dynamicColumn.Index;
if (dynamicColumn.Name != null)
p.ExcelColumnName = dynamicColumn.Name;
isIgnore = dynamicColumn.Ignore;
p.ExcelColumnWidth = dynamicColumn.Width;
}
}
if (!isIgnore)
_props.Add(p);
}
private void SetGenericTypePropertiesMode(Type genericType, ref string mode, out int maxColumnIndex, out List<ExcelColumnInfo> props) private void SetGenericTypePropertiesMode(Type genericType, ref string mode, out int maxColumnIndex, out List<ExcelColumnInfo> props)
{ {
mode = "Properties"; mode = "Properties";
@ -563,16 +521,7 @@ namespace MiniExcelLibs.OpenXml
} }
else if (columnInfo == null || columnInfo.ExcelFormat == null) else if (columnInfo == null || columnInfo.ExcelFormat == null)
{ {
var oaDate = ((DateTime)value).ToOADate(); var oaDate = CorrectDateTimeValue((DateTime)value);
// Excel says 1900 was a leap year :( Replicate an incorrect behavior thanks
// to Lotus 1-2-3 decision from 1983...
// https://github.com/ClosedXML/ClosedXML/blob/develop/ClosedXML/Extensions/DateTimeExtensions.cs#L45
const int nonExistent1900Feb29SerialDate = 60;
if (oaDate <= nonExistent1900Feb29SerialDate)
{
oaDate -= 1;
}
dataType = null; dataType = null;
styleIndex = "3"; styleIndex = "3";
@ -596,16 +545,7 @@ namespace MiniExcelLibs.OpenXml
else if (columnInfo == null || columnInfo.ExcelFormat == null) else if (columnInfo == null || columnInfo.ExcelFormat == null)
{ {
var day = (DateOnly)value; var day = (DateOnly)value;
var oaDate = day.ToDateTime(TimeOnly.MinValue).ToOADate(); var oaDate = CorrectDateTimeValue(day.ToDateTime(TimeOnly.MinValue));
// Excel says 1900 was a leap year :( Replicate an incorrect behavior thanks
// to Lotus 1-2-3 decision from 1983...
// https://github.com/ClosedXML/ClosedXML/blob/develop/ClosedXML/Extensions/DateTimeExtensions.cs#L45
const int nonExistent1900Feb29SerialDate = 60;
if (oaDate <= nonExistent1900Feb29SerialDate)
{
oaDate -= 1;
}
dataType = null; dataType = null;
styleIndex = "3"; styleIndex = "3";
@ -629,6 +569,22 @@ namespace MiniExcelLibs.OpenXml
return Tuple.Create(styleIndex, dataType, cellValue); return Tuple.Create(styleIndex, dataType, cellValue);
} }
private static double CorrectDateTimeValue(DateTime value)
{
var oaDate = value.ToOADate();
// Excel says 1900 was a leap year :( Replicate an incorrect behavior thanks
// to Lotus 1-2-3 decision from 1983...
// https://github.com/ClosedXML/ClosedXML/blob/develop/ClosedXML/Extensions/DateTimeExtensions.cs#L45
const int nonExistent1900Feb29SerialDate = 60;
if (oaDate <= nonExistent1900Feb29SerialDate)
{
oaDate -= 1;
}
return oaDate;
}
private void GenerateSheetByDataTable(MiniExcelStreamWriter writer, DataTable value) private void GenerateSheetByDataTable(MiniExcelStreamWriter writer, DataTable value)
{ {
var xy = ExcelOpenXmlUtils.ConvertCellToXY("A1"); var xy = ExcelOpenXmlUtils.ConvertCellToXY("A1");
@ -834,131 +790,98 @@ namespace MiniExcelLibs.OpenXml
private void GenerateEndXml() private void GenerateEndXml()
{ {
//Files AddFilesToZip();
GenerateStylesXml();
GenerateDrawinRelXml();
GenerateDrawingXml();
GenerateWorkbookXml();
GenerateContentTypesXml();
}
private void AddFilesToZip()
{
foreach (var item in _files)
{ {
foreach (var item in _files) this.CreateZipEntry(item.Path, item.Byte);
{ }
this.CreateZipEntry(item.Path, item.Byte); }
}
/// <summary>
/// styles.xml
/// </summary>
private void GenerateStylesXml()
{
var styleXml = GetStylesXml();
CreateZipEntry(@"xl/styles.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", styleXml);
}
private void GenerateDrawinRelXml()
{
for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++)
{
var drawing = GetDrawingRelationshipXml(sheetIndex);
CreateZipEntry(
$"xl/drawings/_rels/drawing{sheetIndex + 1}.xml.rels",
null,
_defaultDrawingXmlRels.Replace("{{format}}", drawing));
}
}
private void GenerateDrawingXml()
{
for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++)
{
var drawing = GetDrawingXml(sheetIndex);
CreateZipEntry(
$"xl/drawings/drawing{sheetIndex + 1}.xml",
"application/vnd.openxmlformats-officedocument.drawing+xml",
_defaultDrawing.Replace("{{format}}", drawing));
}
}
/// <summary>
/// workbook.xml 、 workbookRelsXml
/// </summary>
private void GenerateWorkbookXml()
{
GenerateWorkBookXmls(
out StringBuilder workbookXml,
out StringBuilder workbookRelsXml,
out Dictionary<int, string> sheetsRelsXml);
foreach (var sheetRelsXml in sheetsRelsXml)
{
CreateZipEntry(
$"xl/worksheets/_rels/sheet{sheetRelsXml.Key}.xml.rels",
null,
_defaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value));
} }
// styles.xml CreateZipEntry(
{ @"xl/workbook.xml",
var styleXml = string.Empty; "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
_defaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()));
if (_configuration.TableStyles == TableStyles.None) CreateZipEntry(
{ @"xl/_rels/workbook.xml.rels",
styleXml = _noneStylesXml; null,
} _defaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()));
else if (_configuration.TableStyles == TableStyles.Default) }
{
styleXml = _defaultStylesXml;
}
CreateZipEntry(@"xl/styles.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", styleXml);
}
// drawing rel /// <summary>
{ /// [Content_Types].xml
for (int j = 0; j < _sheets.Count; j++) /// </summary>
{ private void GenerateContentTypesXml()
var drawing = new StringBuilder(); {
foreach (var i in _files.Where(w => w.IsImage && w.SheetId == j + 1)) var contentTypes = GetContentTypesXml();
{
drawing.AppendLine($@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"" Target=""{i.Path2}"" Id=""{i.ID}"" />");
}
CreateZipEntry($"xl/drawings/_rels/drawing{j + 1}.xml.rels", "",
_defaultDrawingXmlRels.Replace("{{format}}", drawing.ToString()));
}
} CreateZipEntry(@"[Content_Types].xml", null, contentTypes);
// drawing
{
for (int j = 0; j < _sheets.Count; j++)
{
var drawing = new StringBuilder();
foreach (var i in _files.Where(w => w.IsImage && w.SheetId == j + 1))
{
drawing.Append($@"<xdr:oneCellAnchor>
<xdr:from>
<xdr:col>{i.CellIndex - 1/* why -1 : https://user-images.githubusercontent.com/12729184/150460189-f08ed939-44d4-44e1-be6e-9c533ece6be8.png*/}</xdr:col>
<xdr:colOff>0</xdr:colOff>
<xdr:row>{i.RowIndex - 1}</xdr:row>
<xdr:rowOff>0</xdr:rowOff>
</xdr:from>
<xdr:ext cx=""609600"" cy=""190500"" />
<xdr:pic>
<xdr:nvPicPr>
<xdr:cNvPr id=""{_files.IndexOf(i) + 1}"" descr="""" name=""2a3f9147-58ea-4a79-87da-7d6114c4877b"" />
<xdr:cNvPicPr>
<a:picLocks noChangeAspect=""1"" />
</xdr:cNvPicPr>
</xdr:nvPicPr>
<xdr:blipFill>
<a:blip r:embed=""{i.ID}"" cstate=""print"" />
<a:stretch>
<a:fillRect />
</a:stretch>
</xdr:blipFill>
<xdr:spPr>
<a:xfrm>
<a:off x=""0"" y=""0"" />
<a:ext cx=""0"" cy=""0"" />
</a:xfrm>
<a:prstGeom prst=""rect"">
<a:avLst />
</a:prstGeom>
</xdr:spPr>
</xdr:pic>
<xdr:clientData />
</xdr:oneCellAnchor>");
}
CreateZipEntry($"xl/drawings/drawing{j + 1}.xml", "application/vnd.openxmlformats-officedocument.drawing+xml",
_defaultDrawing.Replace("{{format}}", drawing.ToString()));
}
}
// workbook.xml 、 workbookRelsXml
{
var workbookXml = new StringBuilder();
var workbookRelsXml = new StringBuilder();
var sheetId = 0;
foreach (var s in _sheets)
{
sheetId++;
if (string.IsNullOrEmpty(s.State))
{
workbookXml.AppendLine($@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(s.Name)}"" sheetId=""{sheetId}"" r:id=""{s.ID}"" />");
}
else
{
workbookXml.AppendLine($@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(s.Name)}"" sheetId=""{sheetId}"" state=""{s.State}"" r:id=""{s.ID}"" />");
}
workbookRelsXml.AppendLine($@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"" Target=""/{s.Path}"" Id=""{s.ID}"" />");
//TODO: support multiple drawing
//TODO: ../drawings/drawing1.xml or /xl/drawings/drawing1.xml
var sheetRelsXml = $@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"" Target=""../drawings/drawing{sheetId}.xml"" Id=""drawing{sheetId}"" />";
CreateZipEntry($"xl/worksheets/_rels/sheet{s.SheetIdx}.xml.rels", "",
_defaultSheetRelXml.Replace("{{format}}", sheetRelsXml));
}
CreateZipEntry(@"xl/workbook.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
_defaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()));
CreateZipEntry(@"xl/_rels/workbook.xml.rels", "",
_defaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()));
}
//[Content_Types].xml
{
var sb = new StringBuilder(@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><Types xmlns=""http://schemas.openxmlformats.org/package/2006/content-types""><Default ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"" Extension=""bin""/><Default ContentType=""application/xml"" Extension=""xml""/><Default ContentType=""image/jpeg"" Extension=""jpg""/><Default ContentType=""image/png"" Extension=""png""/><Default ContentType=""image/gif"" Extension=""gif""/><Default ContentType=""application/vnd.openxmlformats-package.relationships+xml"" Extension=""rels""/>");
foreach (var p in _zipDictionary)
sb.Append($"<Override ContentType=\"{p.Value.ContentType}\" PartName=\"/{p.Key}\" />");
sb.Append("</Types>");
ZipArchiveEntry entry = _archive.CreateEntry("[Content_Types].xml", CompressionLevel.Fastest);
using (var zipStream = entry.Open())
using (MiniExcelStreamWriter writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize))
writer.Write(sb.ToString());
}
} }
private string GetDimensionRef(int maxRowIndex, int maxColumnIndex) private string GetDimensionRef(int maxRowIndex, int maxColumnIndex)
@ -975,9 +898,26 @@ namespace MiniExcelLibs.OpenXml
return dimensionRef; return dimensionRef;
} }
private void CreateZipEntry(string path, string contentType, string content)
{
ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest);
using (var zipStream = entry.Open())
using (MiniExcelStreamWriter writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize))
writer.Write(content);
if (!string.IsNullOrEmpty(contentType))
_zipDictionary.Add(path, new ZipPackageInfo(entry, contentType));
}
private void CreateZipEntry(string path, byte[] content)
{
ZipArchiveEntry entry = _archive.CreateEntry(path, CompressionLevel.Fastest);
using (var zipStream = entry.Open())
zipStream.Write(content, 0, content.Length);
}
public void Insert() public void Insert()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
} }

View File

@ -3,6 +3,7 @@
using MiniExcelLibs.Attributes; using MiniExcelLibs.Attributes;
using MiniExcelLibs.OpenXml; using MiniExcelLibs.OpenXml;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
@ -168,7 +169,6 @@
return source.ToString(); return source.ToString();
} }
private static IEnumerable<ExcelColumnInfo> ConvertToExcelCustomPropertyInfo(PropertyInfo[] props, Configuration configuration) private static IEnumerable<ExcelColumnInfo> ConvertToExcelCustomPropertyInfo(PropertyInfo[] props, Configuration configuration)
{ {
// solve : https://github.com/shps951023/MiniExcel/issues/138 // solve : https://github.com/shps951023/MiniExcel/issues/138
@ -214,7 +214,6 @@
return ConvertToExcelCustomPropertyInfo(type.GetProperties(bindingFlags), configuration); return ConvertToExcelCustomPropertyInfo(type.GetProperties(bindingFlags), configuration);
} }
internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration configuration) internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration configuration)
{ {
// default options // default options
@ -247,5 +246,65 @@
return sheetInfo; return sheetInfo;
} }
internal static List<ExcelColumnInfo> GetDictionaryColumnInfo(IDictionary<string, object> dicString, IDictionary dic, Configuration configuration)
{
List<ExcelColumnInfo> props;
var _props = new List<ExcelColumnInfo>();
if (dicString != null)
{
foreach (var key in dicString.Keys)
{
SetDictionaryColumnInfo(_props, key, configuration);
}
}
else if (dic != null)
{
foreach (var key in dic.Keys)
{
SetDictionaryColumnInfo(_props, key, configuration);
}
}
else
{
throw new NotSupportedException("SetDictionaryColumnInfo Error");
}
props = SortCustomProps(_props);
return props;
}
internal static void SetDictionaryColumnInfo(List<ExcelColumnInfo> _props, object key, Configuration configuration)
{
var p = new ExcelColumnInfo();
p.ExcelColumnName = key?.ToString();
p.Key = key;
// TODO:Dictionary value type is not fiexed
//var _t =
//var gt = Nullable.GetUnderlyingType(p.PropertyType);
var isIgnore = false;
if (configuration.DynamicColumns != null && configuration.DynamicColumns.Length > 0)
{
var dynamicColumn = configuration.DynamicColumns.SingleOrDefault(_ => _.Key == key.ToString());
if (dynamicColumn != null)
{
p.Nullable = true;
//p.ExcludeNullableType = item2[key]?.GetType();
if (dynamicColumn.Format != null)
p.ExcelFormat = dynamicColumn.Format;
if (dynamicColumn.Aliases != null)
p.ExcelColumnAliases = dynamicColumn.Aliases;
if (dynamicColumn.IndexName != null)
p.ExcelIndexName = dynamicColumn.IndexName;
p.ExcelColumnIndex = dynamicColumn.Index;
if (dynamicColumn.Name != null)
p.ExcelColumnName = dynamicColumn.Name;
isIgnore = dynamicColumn.Ignore;
p.ExcelColumnWidth = dynamicColumn.Width;
}
}
if (!isIgnore)
_props.Add(p);
}
} }
} }