- SavaAs support xl/sheet dimension
- [Breaking Changes] SaveAs value type from object to DataTable & ICollection
- Bug fix: ICollection with type but no data error (https://github.com/shps951023/MiniExcel/issues/105)
This commit is contained in:
wei 2021-03-18 11:41:22 +08:00
parent 1d33ccdce8
commit 786e2a82bd
6 changed files with 220 additions and 30 deletions

View File

@ -2,6 +2,11 @@
## Release Notes ## Release Notes
### 0.2.2
- SavaAs support xl/sheet dimension
- [Breaking Changes] SaveAs value type from object to DataTable & ICollection
- Bug fix: ICollection with type but no data error (https://github.com/shps951023/MiniExcel/issues/105)
### 0.2.1 ### 0.2.1
- Optimize type mapping bool and datetime auto check - Optimize type mapping bool and datetime auto check
- Query Support xl/worksheets/Sheet Xml Xml `<c>` without `r` attribute or without `<dimension>` but `<c>` with `r` attribute, but now performance is slow than with dimension ([](https://github.com/shps951023/MiniExcel/issues/2)) - Query Support xl/worksheets/Sheet Xml Xml `<c>` without `r` attribute or without `<dimension>` but `<c>` with `r` attribute, but now performance is slow than with dimension ([](https://github.com/shps951023/MiniExcel/issues/2))

View File

@ -0,0 +1,108 @@
<Query Kind="Program">
<Connection>
<ID>5fffb9dc-a56f-4ffa-a582-f9da6bc9fdad</ID>
<Persist>true</Persist>
<Server>192.168.1.4</Server>
<SqlSecurity>true</SqlSecurity>
<UserName>sa</UserName>
<Password>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAyumoRWrbXEqda8ynsoawYAAAAAACAAAAAAAQZgAAAAEAACAAAACumZoBhp4lj0R4mTg98suX0pykwksNIARbRIh49xu5/QAAAAAOgAAAAAIAACAAAADoNABocqodkXYmDtdW0GqBvGuMfAJeL++I3kdCYqM4rxAAAADANn2PCQ6OByhczsa8iMQPQAAAAKx4dlXxPcHN4uDHZRcYbnhkQZ52tjk6YEm+q+GruBVhVPrtz22hjCT4VMaK2N6EtZF2Rfr2P8fUTQH/ZPns5GA=</Password>
<Database>kn2015</Database>
</Connection>
<NuGetReference>Dapper</NuGetReference>
<NuGetReference>MiniExcel</NuGetReference>
<NuGetReference>System.Data.SqlClient</NuGetReference>
<Namespace>Xunit</Namespace>
<RemoveNamespace>System.Data</RemoveNamespace>
<RemoveNamespace>System.Diagnostics</RemoveNamespace>
<RemoveNamespace>System.IO</RemoveNamespace>
<RemoveNamespace>System.Linq.Expressions</RemoveNamespace>
<RemoveNamespace>System.Text</RemoveNamespace>
<RemoveNamespace>System.Text.RegularExpressions</RemoveNamespace>
<RemoveNamespace>System.Threading</RemoveNamespace>
<RemoveNamespace>System.Transactions</RemoveNamespace>
<RemoveNamespace>System.Xml</RemoveNamespace>
<RemoveNamespace>System.Xml.Linq</RemoveNamespace>
<RemoveNamespace>System.Xml.XPath</RemoveNamespace>
</Query>
//[c# Reflection - Find the Generic Type of a Collection - Stack Overflow](https://stackoverflow.com/questions/2561070/c-sharp-reflection-find-the-generic-type-of-a-collection)
#load "xunit"
#region private::Tests
[Fact]
void ValueGenericTypeTest()
{
var strings = new List<int>();
var props = Helpers.GetSubtypeGetProperties(strings);
Assert.Equal(0,props.Length);
}
public class TestType
{
public string A { get; set; }
public string B { get; set; }
}
[Fact()]
void IListUpcastingTest()
{
IList datas = new List<TestType>();
var props = Helpers.GetSubtypeGetProperties(datas).ToList();
Assert.Equal(2, props.Count());
Assert.Equal("A", props[0].Name);
Assert.Equal("B", props[1].Name);
}
[Fact()]
void ArrayTest()
{
ICollection datas = new[] { new { A = "1", B = "2" } };
var props = Helpers.GetSubtypeGetProperties(datas);
Assert.Equal(2, props.Count());
}
[Fact()]
void OnlyValidOGenericTypes_Test()
{
ICollection datas = new[] { new { A = "1", B = "2" } };
var df = datas.GetType().GetGenericTypeDefinition(); //InvalidOperationException: This operation is only valid on generic types.
}
[Fact()]
void DictionaryTest()
{
ICollection datas = new[] { new Dictionary<string, object>() { { "A", "A" }, { "B", "B" } } };
var props = Helpers.GetSubtypeGetProperties(datas);
}
#endregion
internal static class Helpers
{
public static PropertyInfo[] GetSubtypeGetProperties(ICollection value)
{
var collectionType = value.GetType();
Type gType;
if (collectionType.IsGenericTypeDefinition || collectionType.IsGenericType)
gType = collectionType.GetGenericArguments().Single();
else if (collectionType.IsArray)
gType = collectionType.GetElementType();
else
throw new NotImplementedException($"{collectionType.Name} type not implemented,please issue for me, https://github.com/shps951023/MiniExcel/issues");
if (typeof(IDictionary).IsAssignableFrom(gType))
throw new NotImplementedException($"{gType.Name} type not implemented,please issue for me, https://github.com/shps951023/MiniExcel/issues");
var props = gType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
return props;
}
}
void Main()
{
//RunTests(); // Call RunTests() or press Alt+Shift+T to initiate testing.
}
// You can define other methods, fields, classes and namespaces here

View File

@ -12,6 +12,7 @@
using System.Reflection; using System.Reflection;
using MiniExcelLibs.Utils; using MiniExcelLibs.Utils;
using System.Globalization; using System.Globalization;
using System.Collections;
public static partial class MiniExcel public static partial class MiniExcel
{ {
@ -26,13 +27,24 @@
private readonly static UTF8Encoding Utf8WithBom = new System.Text.UTF8Encoding(true); private readonly static UTF8Encoding Utf8WithBom = new System.Text.UTF8Encoding(true);
public static void SaveAs(this Stream stream,object value, string startCell = "A1", bool printHeader = true) public static void SaveAs(this Stream stream, DataTable value, string startCell = "A1", bool printHeader = true)
{ {
SaveAsImpl(stream,GetCreateXlsxInfos(value, startCell, printHeader)); SaveAsImpl(stream, GetCreateXlsxInfos(value, startCell, printHeader));
stream.Position = 0; stream.Position = 0;
} }
public static void SaveAs(string filePath, object value, string startCell = "A1", bool printHeader = true) public static void SaveAs(this Stream stream, ICollection value, string startCell = "A1", bool printHeader = true)
{
SaveAsImpl(stream, GetCreateXlsxInfos(value, startCell, printHeader));
stream.Position = 0;
}
public static void SaveAs(string filePath, DataTable value, string startCell = "A1", bool printHeader = true)
{
SaveAsImpl(filePath, GetCreateXlsxInfos(value, startCell, printHeader));
}
public static void SaveAs(string filePath, ICollection value, string startCell = "A1", bool printHeader = true)
{ {
SaveAsImpl(filePath, GetCreateXlsxInfos(value, startCell, printHeader)); SaveAsImpl(filePath, GetCreateXlsxInfos(value, startCell, printHeader));
} }
@ -42,7 +54,12 @@
var xy = ExcelOpenXmlUtils.ConvertCellToXY(startCell); var xy = ExcelOpenXmlUtils.ConvertCellToXY(startCell);
var defaultFiles = GetDefaultFiles(); var defaultFiles = GetDefaultFiles();
var dimensionRef = string.Empty;
// dimension
var dimensionRef = "A1";
var maxRowIndex = 0;
var maxColumnIndex = 0;
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -52,17 +69,10 @@
{ {
var dt = value as DataTable; var dt = value as DataTable;
var maxRowIndex = dt.Rows.Count;
var maxColumnIndex = dt.Columns.Count;
// dimension // dimension
{ maxRowIndex = dt.Rows.Count;
if (maxRowIndex == 0 && maxColumnIndex == 0) maxColumnIndex = dt.Columns.Count;
dimensionRef = "A1";
else if ( maxColumnIndex == 1)
dimensionRef = $"A{maxRowIndex}";
else
dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex-1)}{maxRowIndex}";
}
if (printHeader) if (printHeader)
{ {
@ -119,16 +129,12 @@
else if (value is System.Collections.ICollection) else if (value is System.Collections.ICollection)
{ {
var collection = value as System.Collections.ICollection; var collection = value as System.Collections.ICollection;
object firstValue = null;
{ var props = Helpers.GetSubtypeProperties(collection);
foreach (var v in collection) maxColumnIndex = props.Length;
{ if (props.Length == 0)
firstValue = v; throw new InvalidOperationException($"Properties count is 0");
break;
}
}
var type = firstValue.GetType();
var props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (printHeader) if (printHeader)
{ {
sb.AppendLine($"<x:row r=\"{yIndex.ToString()}\">"); sb.AppendLine($"<x:row r=\"{yIndex.ToString()}\">");
@ -179,6 +185,17 @@
sb.AppendLine($"</x:row>"); sb.AppendLine($"</x:row>");
yIndex++; yIndex++;
} }
maxRowIndex = yIndex-1;
}
// dimension
{
if (maxRowIndex == 0 && maxColumnIndex == 0)
dimensionRef = "A1";
else if (maxColumnIndex == 1)
dimensionRef = $"A{maxRowIndex}";
else
dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}";
} }
defaultFiles[@"xl/worksheets/sheet1.xml"].Xml = $@"<?xml version=""1.0"" encoding=""utf-8""?> defaultFiles[@"xl/worksheets/sheet1.xml"].Xml = $@"<?xml version=""1.0"" encoding=""utf-8""?>
@ -191,7 +208,7 @@
return defaultFiles; return defaultFiles;
} }
public static IEnumerable<T> Query<T>(this Stream stream) where T : class , new() public static IEnumerable<T> Query<T>(this Stream stream) where T : class, new()
{ {
return QueryImpl<T>(stream); return QueryImpl<T>(stream);
} }
@ -294,10 +311,10 @@
private static void SaveAsImpl(string path, Dictionary<string, ZipPackageInfo> zipPackageInfos) private static void SaveAsImpl(string path, Dictionary<string, ZipPackageInfo> zipPackageInfos)
{ {
using (FileStream stream = new FileStream(path, FileMode.CreateNew)) using (FileStream stream = new FileStream(path, FileMode.CreateNew))
using(ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, false, Utf8WithBom)) using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, false, Utf8WithBom))
CreteXlsxImpl(zipPackageInfos, archive); CreteXlsxImpl(zipPackageInfos, archive);
} }
private static void SaveAsImpl(Stream stream,Dictionary<string, ZipPackageInfo> zipPackageInfos) private static void SaveAsImpl(Stream stream, Dictionary<string, ZipPackageInfo> zipPackageInfos)
{ {
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true, Utf8WithBom)) using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true, Utf8WithBom))
{ {

View File

@ -5,7 +5,7 @@
<Product>MiniExcel</Product> <Product>MiniExcel</Product>
<PackageTags>excel;xlsx;micro-helper;mini;openxml;helper;</PackageTags> <PackageTags>excel;xlsx;micro-helper;mini;openxml;helper;</PackageTags>
<Description> <Description>
A high performance Excel Xlsx Micro-Helper without any third party library and supporting create and dynamic/type mapping query etc.. A high performance Excel Xlsx Micro-Helper without any third party library to create and dynamic/type mapping query etc..
Github : https://github.com/shps951023/MiniExcel Github : https://github.com/shps951023/MiniExcel
Issues : https://github.com/shps951023/MiniExcel/issues Issues : https://github.com/shps951023/MiniExcel/issues
@ -19,7 +19,7 @@
<RepositoryUrl>https://github.com/shps951023/MiniExcel</RepositoryUrl> <RepositoryUrl>https://github.com/shps951023/MiniExcel</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/shps951023/ImageHosting/master/img/2019-01-17.13.18.32-image.png</PackageIconUrl> <PackageIconUrl>https://raw.githubusercontent.com/shps951023/ImageHosting/master/img/2019-01-17.13.18.32-image.png</PackageIconUrl>
<TargetFrameworks>net461;netstandard2.0;net5.0</TargetFrameworks> <TargetFrameworks>net461;netstandard2.0;net5.0</TargetFrameworks>
<Version>0.2.1</Version> <Version>0.2.2</Version>
<PackageReleaseNotes>Please Check [Release Notes](https://github.com/shps951023/MiniExcel/tree/master/docs)</PackageReleaseNotes> <PackageReleaseNotes>Please Check [Release Notes](https://github.com/shps951023/MiniExcel/tree/master/docs)</PackageReleaseNotes>
<RepositoryType>Github</RepositoryType> <RepositoryType>Github</RepositoryType>
</PropertyGroup> </PropertyGroup>

View File

@ -4,6 +4,7 @@
namespace MiniExcelLibs.Utils namespace MiniExcelLibs.Utils
{ {
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic; using System.Dynamic;
using System.Globalization; using System.Globalization;
@ -64,13 +65,29 @@ namespace MiniExcelLibs.Utils
return cell; return cell;
} }
public static IEnumerable<PropertyInfo> GetPropertiesWithSetter(Type type) public static IEnumerable<PropertyInfo> GetPropertiesWithSetter(this Type type)
{ {
return type.GetProperties(BindingFlags.SetProperty | return type.GetProperties(BindingFlags.SetProperty |
BindingFlags.Public | BindingFlags.Public |
BindingFlags.Instance).Where(prop => prop.GetSetMethod() != null); BindingFlags.Instance).Where(prop => prop.GetSetMethod() != null);
} }
public static PropertyInfo[] GetSubtypeProperties(ICollection value)
{
var collectionType = value.GetType();
Type gType;
if (collectionType.IsGenericTypeDefinition || collectionType.IsGenericType)
gType = collectionType.GetGenericArguments().Single();
else if (collectionType.IsArray)
gType = collectionType.GetElementType();
else
throw new NotImplementedException($"{collectionType.Name} type not implemented,please issue for me, https://github.com/shps951023/MiniExcel/issues");
if (typeof(IDictionary).IsAssignableFrom(gType))
throw new NotImplementedException($"{gType.Name} type not implemented,please issue for me, https://github.com/shps951023/MiniExcel/issues");
var props = gType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
return props;
}
public static string ConvertEscapeChars(string input) public static string ConvertEscapeChars(string input)
{ {

View File

@ -270,6 +270,48 @@ namespace MiniExcelLibs.Tests
} }
} }
public class SaveAsFileWithDimensionByICollectionTestType
{
public string A { get; set; }
public string B { get; set; }
}
[Fact()]
public void SaveAsFileWithDimensionByICollection()
{
//List<strongtype>
{
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var values = new List<SaveAsFileWithDimensionByICollectionTestType>()
{
new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"},
new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"},
};
MiniExcel.SaveAs(path, values);
Assert.Equal("A1:B3", GetFirstSheetDimensionRefValue(path));
File.Delete(path);
}
//Array<anoymous>
{
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var values = new []
{
new {A="A",B="B"},
new {A="A",B="B"},
};
MiniExcel.SaveAs(path, values);
Assert.Equal("A1:B3", GetFirstSheetDimensionRefValue(path));
File.Delete(path);
}
// without properties
{
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var values = new List<int>();
Assert.Throws<InvalidOperationException>(() => MiniExcel.SaveAs(path, values));
}
}
[Fact()] [Fact()]
public void SaveAsFileWithDimension() public void SaveAsFileWithDimension()
{ {
@ -368,6 +410,7 @@ namespace MiniExcelLibs.Tests
{ {
var now = DateTime.Now; var now = DateTime.Now;
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var table = new DataTable(); var table = new DataTable();
{ {
table.Columns.Add("a", typeof(string)); table.Columns.Add("a", typeof(string));