├── release.ps1 ├── Chsword.Excel2Object.Tests ├── AssemblyConfig.cs ├── ExcelDir │ ├── test.person.xlsx │ ├── test-issue32-skipline.xlsx │ ├── test.person.unnullable.xlsx │ ├── test-pr28-multiples-heet.xlsx │ └── test.person.special-char.xlsx ├── ExpressionConvertConditionTests.cs ├── BaseExcelTest.cs ├── ExpressionConvertTextTests.cs ├── ExpressionConvertMathFunctionsTest.cs ├── NPOITest.cs ├── ExpressionConvertStatisticsTests.cs ├── BaseFunctionTest.cs ├── Models │ ├── Report.cs │ ├── ReportModelCollection.cs │ └── TestModelPerson.cs ├── ExcelColumnNameParserTests.cs ├── Issue32SkipLineImport.cs ├── Issue37SpecialCharTest.cs ├── ExportDateFormatTest.cs ├── Issue12FirstColumnEmptyTest.cs ├── ExpressionConvertReferenceTests.cs ├── Pr20Test.cs ├── Issue39DynamicMappingTitle.cs ├── Chsword.Excel2Object.Tests.csproj ├── Pr24NullableTest.cs ├── Pr28MultipleSheetTest.cs ├── ExpressionConvertTest.cs ├── Issue16FormulaTest.cs ├── Issue31SuperClass.cs ├── ExpressionConvertSymbolTests.cs ├── SimpleAutoWidthTest.cs ├── ExcelTest.cs ├── AutoColumnWidthTest.cs └── DateTimeFormatTest.cs ├── Chsword.Excel2Object ├── AssemblyConfig.cs ├── Chsword.Excel2Object.csproj ├── Models │ ├── ExcelModel.cs │ ├── SheetModel.cs │ └── ExcelColumn.cs ├── Functions │ ├── IConditionFunction.cs │ ├── IAllFunction.cs │ ├── ColumnMatrix.cs │ ├── IStatisticsFunction.cs │ ├── IReferenceFunction.cs │ ├── IMathFunction.cs │ ├── IDateTimeFunction.cs │ ├── ColumnCellDictionary.cs │ ├── ITextFunction.cs │ └── ColumnValue.cs ├── Options │ ├── ExcelImporterOptions.cs │ ├── FormulaColumn.cs │ ├── ExcelExporterOptions.cs │ └── FormulaColumnsCollection.cs ├── Styles │ ├── HorizontalAlignment.cs │ ├── IExcelHeaderStyle.cs │ ├── IExcelCellStyle.cs │ └── ExcelStyleColor.cs ├── ExcelType.cs ├── Excel2ObjectException.cs ├── Chsword.Excel2Object.csproj.DotSettings ├── Internal │ ├── ExcelColumnNameParser.cs │ ├── TypeUtil.cs │ ├── ExcelUtil.cs │ ├── ExcelConstants.cs │ ├── TypeConvert.cs │ └── ExpressionConvert.cs ├── ExcelFunctions.cs ├── ExcelTitleAttribute.cs ├── ExcelColumnAttribute.cs └── ExcelHelper.cs ├── Excel2Object.code-workspace ├── Chsword.Excel2Object.sln.DotSettings ├── LICENSE ├── Chsword.Excel2Object.sln ├── .github ├── workflows │ ├── dotnet-ci.yml │ └── release.yml ├── QUICK_REFERENCE.md ├── copilot-instructions.md ├── RELEASE_CHECKLIST.md └── VERSIONING.md ├── .gitattributes ├── CODE_OF_CONDUCT.md ├── ExcelFunctions.md ├── RELEASE_SCRIPT_GUIDE.md ├── AutoColumnWidthDemo.cs ├── ROADMAP.md ├── .gitignore ├── DateTimeFormats.md ├── README.md ├── release.ps1.backup └── README_EN.md /release.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsword/Excel2Object/HEAD/release.ps1 -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/AssemblyConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Chsword.Excel2Object")] -------------------------------------------------------------------------------- /Chsword.Excel2Object/AssemblyConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Chsword.Excel2Object.Tests")] -------------------------------------------------------------------------------- /Chsword.Excel2Object/Chsword.Excel2Object.csproj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsword/Excel2Object/HEAD/Chsword.Excel2Object/Chsword.Excel2Object.csproj -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExcelDir/test.person.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsword/Excel2Object/HEAD/Chsword.Excel2Object.Tests/ExcelDir/test.person.xlsx -------------------------------------------------------------------------------- /Chsword.Excel2Object/Models/ExcelModel.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object; 2 | 3 | internal class ExcelModel 4 | { 5 | public List? Sheets { get; set; } 6 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExcelDir/test-issue32-skipline.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsword/Excel2Object/HEAD/Chsword.Excel2Object.Tests/ExcelDir/test-issue32-skipline.xlsx -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExcelDir/test.person.unnullable.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsword/Excel2Object/HEAD/Chsword.Excel2Object.Tests/ExcelDir/test.person.unnullable.xlsx -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExcelDir/test-pr28-multiples-heet.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsword/Excel2Object/HEAD/Chsword.Excel2Object.Tests/ExcelDir/test-pr28-multiples-heet.xlsx -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExcelDir/test.person.special-char.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsword/Excel2Object/HEAD/Chsword.Excel2Object.Tests/ExcelDir/test.person.special-char.xlsx -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/IConditionFunction.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | public interface IConditionFunction 4 | { 5 | ColumnValue If(ColumnValue condition, ColumnValue value1, ColumnValue value2); 6 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Options/ExcelImporterOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Options; 2 | 3 | public class ExcelImporterOptions 4 | { 5 | public string? SheetTitle { get; set; } 6 | public int TitleSkipLine { get; set; } 7 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/IAllFunction.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | public interface IAllFunction : IMathFunction, IStatisticsFunction, IConditionFunction, IReferenceFunction, 4 | IDateTimeFunction, ITextFunction; -------------------------------------------------------------------------------- /Excel2Object.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "github.copilot.chat.agent.terminal.allowList": { 9 | "powershell": true, 10 | "bash": true, 11 | "dotnet": true 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/ColumnMatrix.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | /// 4 | /// Represents a matrix of column values. 5 | /// 6 | public class ColumnMatrix 7 | { 8 | // Add properties, methods, and fields as needed. 9 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Styles/HorizontalAlignment.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Styles; 2 | 3 | // copy from NPOI.SS.UserModel.HorizontalAlignment 4 | public enum HorizontalAlignment 5 | { 6 | General, 7 | Left, 8 | Center, 9 | Right, 10 | Fill, 11 | Justify, 12 | CenterSelection, 13 | Distributed 14 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/ExcelType.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object; 2 | 3 | /// 4 | /// Excel File Type 5 | /// 6 | public enum ExcelType 7 | { 8 | /// 9 | /// Microsoft Excel 97 - 2003 Document 10 | /// 11 | Xls, 12 | 13 | /// 14 | /// Microsoft Office EXCEL 2007 Document 15 | /// 16 | Xlsx 17 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExpressionConvertConditionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Chsword.Excel2Object.Tests; 4 | 5 | [TestClass] 6 | public class ExpressionConvertConditionTests : BaseFunctionTest 7 | { 8 | [TestMethod] 9 | public void If() 10 | { 11 | TestFunction(c => ExcelFunctions.Condition.If(c["One"] == "Yes", 1, 2), "IF(A4=\"Yes\",1,2)"); 12 | } 13 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Options/FormulaColumn.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Chsword.Excel2Object.Functions; 3 | 4 | namespace Chsword.Excel2Object.Options; 5 | 6 | public class FormulaColumn 7 | { 8 | public string? AfterColumnTitle { get; set; } 9 | public Expression>? Formula { get; set; } 10 | 11 | public Type? FormulaResultType { get; set; } 12 | public string? Title { get; set; } 13 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/BaseExcelTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Chsword.Excel2Object.Tests; 5 | 6 | public class BaseExcelTest 7 | { 8 | protected string GetFilePath(string file) 9 | { 10 | return Path.Combine(Environment.CurrentDirectory, file); 11 | } 12 | 13 | protected string GetLocalFilePath(string file) 14 | { 15 | return Path.Combine(Environment.CurrentDirectory, "ExcelDir", file); 16 | } 17 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExpressionConvertTextTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Chsword.Excel2Object.Tests; 4 | 5 | [TestClass] 6 | public class ExpressionConvertTextTests : BaseFunctionTest 7 | { 8 | [TestMethod] 9 | public void Find() 10 | { 11 | TestFunction(c => ExcelFunctions.Text.Find("M", c["One", 2]), "FIND(\"M\",A2)"); 12 | TestFunction(c => ExcelFunctions.Text.Find("M", c["One", 2], 2), "FIND(\"M\",A2,2)"); 13 | } 14 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Styles/IExcelHeaderStyle.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Styles; 2 | 3 | public interface IExcelHeaderStyle 4 | { 5 | HorizontalAlignment HeaderAlignment { get; set; } 6 | bool HeaderBold { get; set; } 7 | ExcelStyleColor HeaderFontColor { get; set; } 8 | string? HeaderFontFamily { get; set; } 9 | double HeaderFontHeight { get; set; } 10 | bool HeaderItalic { get; set; } 11 | bool HeaderStrikeout { get; set; } 12 | bool HeaderUnderline { get; set; } 13 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Styles/IExcelCellStyle.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Styles; 2 | 3 | public interface IExcelCellStyle 4 | { 5 | HorizontalAlignment CellAlignment { get; set; } 6 | bool CellBold { get; set; } 7 | ExcelStyleColor CellFontColor { get; set; } 8 | string? CellFontFamily { get; set; } 9 | double CellFontHeight { get; set; } 10 | bool CellItalic { get; set; } 11 | bool CellStrikeout { get; set; } 12 | bool CellUnderline { get; set; } 13 | 14 | string? Format { get; set; } 15 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Models/SheetModel.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object; 2 | 3 | internal class SheetModel 4 | { 5 | public List Columns { get; set; } = new(); 6 | public int Index { get; set; } 7 | public List> Rows { get; set; } = new(); 8 | public string Title { get; private set; } = null!; 9 | 10 | public static SheetModel Create(string? title) 11 | { 12 | return new SheetModel 13 | { 14 | Title = title ?? "Sheet1" 15 | }; 16 | } 17 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExpressionConvertMathFunctionsTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Chsword.Excel2Object.Tests; 4 | 5 | [TestClass] 6 | public class ExpressionConvertMathFunctionsTests : BaseFunctionTest 7 | { 8 | [TestMethod] 9 | public void AbsTest() 10 | { 11 | TestFunction(c => ExcelFunctions.Math.Abs(c["One"]), "ABS(A4)"); 12 | } 13 | 14 | [TestMethod] 15 | public void PITest() 16 | { 17 | TestFunction(c => ExcelFunctions.Math.PI(), "PI()"); 18 | } 19 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Excel2ObjectException.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object; 2 | 3 | /// 4 | /// Represents errors that occur during Excel to object conversion. 5 | /// 6 | public class Excel2ObjectException : Exception 7 | { 8 | /// 9 | /// Initializes a new instance of the class with a specified error message. 10 | /// 11 | /// The message that describes the error. 12 | public Excel2ObjectException(string message) : base(message) 13 | { 14 | } 15 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/IStatisticsFunction.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | /// 4 | /// Interface for statistical functions used in Excel to object conversion. 5 | /// 6 | public interface IStatisticsFunction 7 | { 8 | /// 9 | /// Calculates the sum of the specified matrices. 10 | /// 11 | /// The matrices to sum. 12 | /// A representing the sum of the matrices. 13 | ColumnValue Sum(params ColumnMatrix[] matrix); 14 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/NPOITest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Chsword.Excel2Object.Styles; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Chsword.Excel2Object.Tests; 6 | 7 | [TestClass] 8 | public class NPOITest 9 | { 10 | [TestMethod] 11 | public void CheckHorizontalAlignment() 12 | { 13 | var localNames = Enum.GetNames(typeof(HorizontalAlignment)); 14 | var npoiNames = Enum.GetNames(typeof(NPOI.SS.UserModel.HorizontalAlignment)); 15 | Assert.AreEqual(string.Join(",", localNames), string.Join(",", npoiNames)); 16 | } 17 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Chsword.Excel2Object.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | Latest 3 | True -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExpressionConvertStatisticsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Chsword.Excel2Object.Tests; 4 | 5 | [TestClass] 6 | public class ExpressionConvertStatisticsTests : BaseFunctionTest 7 | { 8 | [TestMethod] 9 | public void Sum() 10 | { 11 | TestFunction(c => ExcelFunctions.Statistics.Sum(c.Matrix("One", 1, "Two", 2)), "SUM(A1:B2)"); 12 | TestFunction( 13 | c => ExcelFunctions.Statistics.Sum(c.Matrix("One", 1, "Two", 2), c.Matrix("Six", 11, "Five", 2)), 14 | "SUM(A1:B2,F11:E2)"); 15 | } 16 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Internal/ExcelColumnNameParser.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Internal; 2 | 3 | internal static class ExcelColumnNameParser 4 | { 5 | public static string Parse(int columnIndex, int initValue = 0) 6 | { 7 | var x = columnIndex; 8 | var list = new List(); 9 | 10 | do 11 | { 12 | var mod = x % 26; 13 | if (x != columnIndex) mod -= 1; 14 | 15 | x /= 26; 16 | list.Add((char) ('A' + mod)); 17 | } while (x > 0); 18 | 19 | list.Reverse(); 20 | return string.Join("", list); 21 | } 22 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/IReferenceFunction.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | public interface IReferenceFunction 4 | { 5 | ColumnValue Choose(ColumnValue indexNum, params ColumnValue[] values); 6 | ColumnValue Index(ColumnMatrix array, ColumnValue rowNum, ColumnValue columnNum); 7 | ColumnValue Lookup(ColumnValue val, ColumnMatrix lookupVector, ColumnMatrix resultVector); 8 | 9 | ColumnValue Match(ColumnValue val, ColumnMatrix tableArray, int matchType); 10 | 11 | ColumnValue VLookup(ColumnValue val, ColumnMatrix tableArray, ColumnValue colIndexNum, 12 | bool rangeLookup = false); 13 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/BaseFunctionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using Chsword.Excel2Object.Functions; 4 | using Chsword.Excel2Object.Internal; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace Chsword.Excel2Object.Tests; 8 | 9 | public class BaseFunctionTest 10 | { 11 | protected void TestFunction(Expression> exp, string expected) 12 | { 13 | var convert = new ExpressionConvert(new[] {"One", "Two", "Three", "Four", "Five", "Six"}, 3); 14 | var ret = convert.Convert(exp); 15 | Assert.AreEqual(expected, ret); 16 | } 17 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/ExcelFunctions.cs: -------------------------------------------------------------------------------- 1 | using Chsword.Excel2Object.Functions; 2 | 3 | namespace Chsword.Excel2Object; 4 | 5 | public static class ExcelFunctions 6 | { 7 | public static IAllFunction All { get; set; } = null!; 8 | public static IConditionFunction Condition { get; set; } = null!; 9 | public static IDateTimeFunction DateAndTime { get; set; } = null!; 10 | 11 | public static IMathFunction Math { get; set; } = null!; 12 | public static IReferenceFunction Reference { get; set; } = null!; 13 | public static IStatisticsFunction Statistics { get; set; } = null!; 14 | public static ITextFunction Text { get; set; } = null!; 15 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Models/Report.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable All 4 | 5 | namespace Chsword.Excel2Object.Tests.Models; 6 | 7 | [ExcelTitle("Test Sheetname")] 8 | public class ReportModel 9 | { 10 | [ExcelTitle("Open")] public bool? Enabled { get; set; } 11 | 12 | [ExcelTitle("User Name")] public string Name { get; set; } 13 | 14 | [ExcelTitle("Document Title")] public string Title { get; set; } 15 | 16 | [ExcelTitle("Type")] public MyEnum Type { get; set; } 17 | 18 | [ExcelTitle("Address")] public Uri Uri { get; set; } 19 | } 20 | 21 | public enum MyEnum 22 | { 23 | Unkonw = 0, 24 | 一 = 1, 25 | 二 = 2, 26 | 三 = 3 27 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Models/ExcelColumn.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Chsword.Excel2Object.Functions; 3 | using Chsword.Excel2Object.Styles; 4 | 5 | namespace Chsword.Excel2Object; 6 | 7 | internal class ExcelColumn 8 | { 9 | public IExcelCellStyle? CellStyle { get; set; } 10 | 11 | public Expression>? Formula { get; set; } 12 | 13 | public IExcelHeaderStyle? HeaderStyle { get; set; } 14 | public int Order { get; set; } 15 | 16 | /// 17 | /// ���ҽ��� Type = Expression ʱ��Ч 18 | /// 19 | public Type? ResultType { get; set; } 20 | 21 | public string? Title { get; set; } 22 | public Type? Type { get; set; } 23 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExcelColumnNameParserTests.cs: -------------------------------------------------------------------------------- 1 | using Chsword.Excel2Object.Internal; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Chsword.Excel2Object.Tests; 5 | 6 | [TestClass] 7 | public class ExcelColumnNameParserTests 8 | { 9 | [TestMethod] 10 | public void Parse() 11 | { 12 | AssertHelper("A", 0); 13 | AssertHelper("B", 1); 14 | AssertHelper("Z", 25); 15 | AssertHelper("AA", 26); 16 | AssertHelper("DL", 115); 17 | AssertHelper("ACM", 766); 18 | } 19 | 20 | private void AssertHelper(string v1, int v2) 21 | { 22 | var ret = ExcelColumnNameParser.Parse(v2); 23 | Assert.AreEqual(v1, ret); 24 | } 25 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/IMathFunction.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | public interface IMathFunction 4 | { 5 | int Abs(object val); 6 | 7 | /// 8 | /// 正数向上取整至偶数,负数向下取整至偶数 9 | /// 10 | int Even(object val); 11 | 12 | //阶fact 13 | int Fact(object val); 14 | 15 | /// 16 | /// To integer 17 | /// 18 | int Int(object val); 19 | 20 | double PI(); 21 | 22 | /// 23 | /// random 0-1 24 | /// 25 | double Rand(); 26 | 27 | int Round(object val, object digits); 28 | int RoundDown(object val, object digits); 29 | int RoundUp(object val, object digits); 30 | int Sqrt(object val); 31 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Internal/TypeUtil.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Chsword.Excel2Object.Internal; 4 | 5 | public static class TypeUtil 6 | { 7 | /// 8 | /// Get a real type of Nullable type 9 | /// 10 | /// 11 | /// 12 | public static Type GetUnNullableType(Type conversionType) 13 | { 14 | if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition() == typeof(Nullable<>)) 15 | { 16 | var nullableConverter = new NullableConverter(conversionType); 17 | conversionType = nullableConverter.UnderlyingType; 18 | } 19 | 20 | return conversionType; 21 | } 22 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Models/ReportModelCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace Chsword.Excel2Object.Tests.Models; 5 | 6 | internal class ReportModelCollection : List 7 | { 8 | public void AreEqual(ICollection reportModels) 9 | { 10 | Assert.AreEqual(Count, reportModels.Count); 11 | var index = 0; 12 | foreach (var model1 in reportModels) 13 | { 14 | var model2 = this[index]; 15 | Assert.AreEqual(model2.Title, model1.Title); 16 | Assert.AreEqual(model2.Name, model1.Name); 17 | Assert.AreEqual(model2.Enabled, model1.Enabled); 18 | index++; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Issue32SkipLineImport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Chsword.Excel2Object.Tests.Models; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Newtonsoft.Json; 6 | 7 | namespace Chsword.Excel2Object.Tests; 8 | 9 | [TestClass] 10 | public class Issue32SkipLineImport : BaseExcelTest 11 | { 12 | [TestMethod] 13 | public void SkipLineImport() 14 | { 15 | var path = GetLocalFilePath("test-issue32-skipline.xlsx"); 16 | var importer = new ExcelImporter(); 17 | var result = 18 | importer.ExcelToObject( 19 | path, options => { options.TitleSkipLine = 3; })! 20 | .ToList(); 21 | Assert.AreEqual(2, result.Count); 22 | Console.WriteLine(JsonConvert.SerializeObject(result)); 23 | } 24 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/ExcelTitleAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object; 2 | 3 | /// 4 | /// On property, it will be a column title. 5 | /// On class, it will be a sheet title. 6 | /// 7 | public class ExcelTitleAttribute : Attribute 8 | { 9 | /// 10 | /// Initializes a new instance of the class. 11 | /// 12 | /// The title of the column or sheet. 13 | public ExcelTitleAttribute(string title) 14 | { 15 | Title = title; 16 | } 17 | 18 | /// 19 | /// Gets or sets the order of the column or sheet. 20 | /// 21 | public int Order { get; set; } 22 | 23 | /// 24 | /// Gets or sets the title of the column or sheet. 25 | /// 26 | public string Title { get; set; } 27 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/IDateTimeFunction.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | public interface IDateTimeFunction 4 | { 5 | ColumnValue Date(ColumnValue year, ColumnValue month, ColumnValue day); 6 | ColumnValue DateDif(ColumnValue start, ColumnValue end, string unit); 7 | ColumnValue Days(ColumnValue start, ColumnValue end); 8 | ColumnValue DateValue(ColumnValue date); 9 | ColumnValue Now(); 10 | ColumnValue Time(ColumnValue hour, ColumnValue minute, ColumnValue second); 11 | ColumnValue TimeValue(ColumnValue time); 12 | ColumnValue Today(); 13 | ColumnValue Weekday(ColumnValue date, ColumnValue firstDay); 14 | ColumnValue Year(ColumnValue date); 15 | ColumnValue YearFrac(ColumnValue start, ColumnValue end, string unit); 16 | ColumnValue Hour(ColumnValue time); 17 | ColumnValue Minute(ColumnValue time); 18 | ColumnValue Second(ColumnValue time); 19 | ColumnValue Month(ColumnValue date); 20 | ColumnValue Day(ColumnValue date); 21 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | NPOI 3 | PI 4 | True 5 | True 6 | True 7 | True 8 | True -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Zou Jian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Issue37SpecialCharTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Newtonsoft.Json; 5 | 6 | namespace Chsword.Excel2Object.Tests; 7 | 8 | [TestClass] 9 | public class Issue37SpecialCharTest : BaseExcelTest 10 | { 11 | [TestMethod] 12 | public void SpecialCharTest() 13 | { 14 | var path = GetLocalFilePath("test.person.special-char.xlsx"); 15 | var importer = new ExcelImporter(); 16 | var result = importer.ExcelToObject(path)!.ToList(); 17 | Assert.AreEqual(2, result.Count); 18 | Assert.AreEqual("100", result[0].Money); 19 | Assert.AreEqual("200", result[1].Money); 20 | Console.WriteLine(JsonConvert.SerializeObject(result)); 21 | } 22 | } 23 | 24 | [ExcelTitle("Test Person")] 25 | public class TestModelPersonSpecialChar 26 | { 27 | [ExcelTitle("姓名$")] public string Name { get; set; } = null!; 28 | [ExcelTitle("$年龄")] public int? Age { get; set; } 29 | [ExcelTitle("出生日期#")] public DateTime? Birthday { get; set; } 30 | [ExcelTitle("金额$")] public string Money { get; set; } = null!; 31 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Models/TestModelPerson.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable All 4 | 5 | namespace Chsword.Excel2Object.Tests.Models; 6 | 7 | [ExcelTitle("Test Person")] 8 | public class TestModelPerson 9 | { 10 | [ExcelTitle("姓名")] public string Name { get; set; } 11 | [ExcelTitle("年龄")] public int? Age { get; set; } 12 | [ExcelTitle("出生日期")] public DateTime? Birthday { get; set; } 13 | [ExcelTitle("创建时间")] public DateTime? CreateTime { get; set; } 14 | } 15 | 16 | [ExcelTitle("Test Strict Person")] 17 | public class TestModelStrictPerson 18 | { 19 | [ExcelTitle("姓名")] public string Name { get; set; } 20 | [ExcelTitle("年龄")] public int Age { get; set; } 21 | [ExcelTitle("出生日期")] public DateTime Birthday { get; set; } 22 | } 23 | 24 | [ExcelTitle("Test Date Person")] 25 | public class TestModelDatePerson 26 | { 27 | [ExcelTitle("姓名")] public string Name { get; set; } 28 | [ExcelTitle("年龄")] public int Age { get; set; } 29 | 30 | [ExcelColumn("出生日期", Format = "yyyy-MM-dd HH:mm:ss")] 31 | public DateTime Birthday { get; set; } 32 | 33 | [ExcelColumn("出生日期2", Format = "yyyy-MM-dd HH:mm:ss")] 34 | public DateTime? Birthday2 { get; set; } 35 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExportDateFormatTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Chsword.Excel2Object.Tests.Models; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using NPOI.HSSF.UserModel; 6 | 7 | namespace Chsword.Excel2Object.Tests; 8 | 9 | [TestClass] 10 | public class ExportDateFormatTest : BaseExcelTest 11 | { 12 | [TestMethod] 13 | public void ExportDateTest() 14 | { 15 | var list = new List 16 | { 17 | new() 18 | { 19 | Age = 18, 20 | Birthday = DateTime.Now, 21 | Birthday2 = DateTime.Now, 22 | Name = "test" 23 | }, 24 | new() 25 | { 26 | Age = 18, 27 | Birthday = DateTime.Now, 28 | 29 | Name = "test2" 30 | } 31 | }; 32 | ExcelHelper.ObjectToExcel(list, GetFilePath(DateTime.Now.Ticks + "test.xls")); 33 | } 34 | 35 | [TestMethod] 36 | public void MyTestMethod() 37 | { 38 | var list = HSSFDataFormat.GetBuiltinFormats(); 39 | foreach (var item in list) Console.WriteLine(item); 40 | } 41 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Styles/ExcelStyleColor.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Styles; 2 | 3 | public enum ExcelStyleColor : short 4 | { 5 | //ColorNormal = 32767, 6 | Black = 8, 7 | Brown = 60, 8 | OliveGreen = 59, 9 | DarkGreen = 58, 10 | DarkTeal = 56, 11 | DarkBlue = 18, 12 | Indigo = 62, 13 | Grey80Percent = 63, 14 | Orange = 53, 15 | DarkYellow = 19, 16 | Green = 17, 17 | Teal = 21, 18 | Blue = 12, 19 | BlueGrey = 54, 20 | Grey50Percent = 23, 21 | Red = 10, 22 | LightOrange = 52, 23 | Lime = 50, 24 | SeaGreen = 57, 25 | Aqua = 49, 26 | LightBlue = 48, 27 | Violet = 20, 28 | Grey40Percent = 55, 29 | Pink = 14, 30 | Gold = 51, 31 | Yellow = 13, 32 | BrightGreen = 11, 33 | Turquoise = 15, 34 | DarkRed = 16, 35 | SkyBlue = 40, 36 | Plum = 61, 37 | Grey25Percent = 22, 38 | Rose = 45, 39 | LightYellow = 43, 40 | LightGreen = 42, 41 | LightTurquoise = 41, 42 | PaleBlue = 44, 43 | Lavender = 46, 44 | White = 9, 45 | CornflowerBlue = 24, 46 | LemonChiffon = 26, 47 | Maroon = 25, 48 | Orchid = 28, 49 | Coral = 29, 50 | RoyalBlue = 30, 51 | LightCornflowerBlue = 31, 52 | Tan = 47 53 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/ColumnCellDictionary.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | /// 4 | /// Represents a dictionary of column cells. 5 | /// 6 | public class ColumnCellDictionary : Dictionary 7 | { 8 | /// 9 | /// Gets the at the specified column name and row number. 10 | /// 11 | /// The name of the column. 12 | /// The number of the row. 13 | /// The at the specified column name and row number. 14 | public ColumnValue this[string columnName, int rowNumber] => throw new NotImplementedException(); 15 | 16 | /// 17 | /// Gets a matrix of column values between the specified keys and row numbers. 18 | /// 19 | /// The first key. 20 | /// The row number for the first key. 21 | /// The second key. 22 | /// The row number for the second key. 23 | /// A representing the matrix of column values. 24 | public ColumnMatrix Matrix(string keyA, int rowA, string keyB, int rowB) 25 | { 26 | throw new NotImplementedException(); 27 | } 28 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Issue12FirstColumnEmptyTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Chsword.Excel2Object.Tests.Models; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace Chsword.Excel2Object.Tests; 7 | 8 | //https://github.com/chsword/Excel2Object/issues/12 9 | [TestClass] 10 | public class Issue12FirstColumnEmptyTest 11 | { 12 | [TestMethod] 13 | public void EmptyFirstProperty() 14 | { 15 | var models = GetModels(); 16 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 17 | Assert.IsNotNull(bytes); 18 | Assert.IsTrue(bytes.Length > 0); 19 | var importer = new ExcelImporter(); 20 | var result = importer.ExcelToObject(bytes).ToList(); 21 | Console.WriteLine(result.FirstOrDefault()); 22 | Assert.AreEqual(models.Count, result.Count); 23 | models.AreEqual(result); 24 | } 25 | 26 | private ReportModelCollection GetModels() 27 | { 28 | return new ReportModelCollection 29 | { 30 | new() 31 | { 32 | Name = "x", Title = "", Enabled = true 33 | }, 34 | new() 35 | { 36 | Name = "y", Title = "", Enabled = false 37 | }, 38 | new() 39 | { 40 | Name = "z", Title = "e", Uri = new Uri("http://chsword.cnblogs.com") 41 | } 42 | }; 43 | } 44 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Options/ExcelExporterOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Options; 2 | 3 | public class ExcelExporterOptions 4 | { 5 | /// 6 | /// Excel file type default:xlsx 7 | /// 8 | public ExcelType ExcelType { get; set; } = ExcelType.Xlsx; 9 | 10 | public FormulaColumnsCollection FormulaColumns { get; set; } = new(); 11 | 12 | /// 13 | /// Sheet Title default:null 14 | /// 15 | public string? SheetTitle { get; set; } 16 | 17 | /// 18 | /// Use when append export 19 | /// 20 | public byte[]? SourceExcelBytes { get; set; } 21 | 22 | public Func? MappingColumnAction { get; set; } 23 | 24 | /// 25 | /// Enable auto column width adjustment based on content 26 | /// 27 | public bool AutoColumnWidth { get; set; } = false; 28 | 29 | /// 30 | /// Minimum column width in characters (default: 8) 31 | /// 32 | public int MinColumnWidth { get; set; } = 8; 33 | 34 | /// 35 | /// Maximum column width in characters (default: 50) 36 | /// 37 | public int MaxColumnWidth { get; set; } = 50; 38 | 39 | /// 40 | /// Default column width in characters when AutoColumnWidth is false (default: 16) 41 | /// 42 | public int DefaultColumnWidth { get; set; } = 16; 43 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/ITextFunction.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Functions; 2 | 3 | /// 4 | /// Interface for text functions used in Excel to object conversion. 5 | /// 6 | public interface ITextFunction 7 | { 8 | /// 9 | /// Converts a string to ASCII values. 10 | /// 11 | /// The string to convert. 12 | /// A representing the ASCII values of the string. 13 | ColumnValue Asc(ColumnValue str); 14 | 15 | /// 16 | /// Finds one text string within another, starting at a specified position. 17 | /// 18 | /// The text to find. 19 | /// The text to search within. 20 | /// The position to start the search from. 21 | /// A representing the position of the found text. 22 | ColumnValue Find(ColumnValue findText, ColumnValue withinText, int startNum); 23 | 24 | /// 25 | /// Finds one text string within another. 26 | /// 27 | /// The text to find. 28 | /// The text to search within. 29 | /// A representing the position of the found text. 30 | ColumnValue Find(ColumnValue findText, ColumnValue withinText); 31 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExpressionConvertReferenceTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Chsword.Excel2Object.Tests; 4 | 5 | [TestClass] 6 | public class ExpressionConvertReferenceTests : BaseFunctionTest 7 | { 8 | [TestMethod] 9 | public void Choose() 10 | { 11 | TestFunction(c => ExcelFunctions.Reference.Choose(2, c["One", 2], c["One", 3] 12 | , c["One", 4], c["One", 5]), "CHOOSE(2,A2,A3,A4,A5)"); 13 | } 14 | 15 | [TestMethod] 16 | public void Index() 17 | { 18 | TestFunction(c => ExcelFunctions.Reference.Index(c.Matrix("One", 2, "Two", 6), 19 | 2, 3), "INDEX(A2:B6,2,3)"); 20 | } 21 | 22 | [TestMethod] 23 | public void Lookup() 24 | { 25 | TestFunction(c => ExcelFunctions.Reference.Lookup(4.19, c.Matrix("One", 2, "One", 6), 26 | c.Matrix("Two", 2, "Two", 6)), "LOOKUP(4.19,A2:A6,B2:B6)"); 27 | } 28 | 29 | [TestMethod] 30 | public void Match() 31 | { 32 | TestFunction(c => ExcelFunctions.Reference.Match(39, c.Matrix("Two", 2, "Two", 5), 33 | 1), "MATCH(39,B2:B5,1)"); 34 | } 35 | 36 | [TestMethod] 37 | public void VLookup() 38 | { 39 | TestFunction(c => ExcelFunctions.Reference.VLookup(c["One"], c.Matrix("One", 10, "Three", 20), 40 | 2, true), "VLOOKUP(A4,A10:C20,2,TRUE)"); 41 | TestFunction(c => ExcelFunctions.Reference.VLookup("袁", c.Matrix("Two", 2, "Five", 7), 42 | 2, false), "VLOOKUP(\"袁\",B2:E7,2,FALSE)"); 43 | //todo seach over sheet ! 44 | // = VLOOKUP (A2,"客户端详细信息"!A:F,3,FALSE) 45 | } 46 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Pr20Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Chsword.Excel2Object.Styles; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace Chsword.Excel2Object.Tests; 8 | 9 | [TestClass] 10 | public class Pr20Test : BaseExcelTest 11 | { 12 | [TestMethod] 13 | public void UseExcelColumnAttr() 14 | { 15 | var list = new List 16 | { 17 | new() 18 | { 19 | Fullname = "AAA", Mobile = "123456798123" 20 | }, 21 | new() 22 | { 23 | Fullname = "BBB", Mobile = "234" 24 | } 25 | }; 26 | var bytes = ExcelHelper.ObjectToExcelBytes(list, ExcelType.Xlsx); 27 | Assert.IsNotNull(bytes); 28 | 29 | var path = GetFilePath("test.xlsx"); 30 | File.WriteAllBytes(path, bytes); 31 | Console.WriteLine(path); 32 | } 33 | 34 | [ExcelTitle("SheetX")] 35 | public class Pr20Model 36 | { 37 | [ExcelColumn("姓名", CellFontColor = ExcelStyleColor.Red)] 38 | public string? Fullname { get; set; } 39 | 40 | [ExcelColumn("手机", 41 | HeaderFontFamily = "宋体", 42 | HeaderBold = true, 43 | HeaderFontHeight = 30, 44 | HeaderItalic = true, 45 | HeaderFontColor = ExcelStyleColor.Blue, 46 | HeaderUnderline = true, 47 | HeaderAlignment = HorizontalAlignment.Right, 48 | //cell 49 | CellAlignment = HorizontalAlignment.Justify 50 | )] 51 | public string Mobile { get; set; } = null!; 52 | } 53 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Issue39DynamicMappingTitle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Chsword.Excel2Object.Tests.Models; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using Newtonsoft.Json; 7 | 8 | namespace Chsword.Excel2Object.Tests; 9 | 10 | [TestClass] 11 | public class Issue39DynamicMappingTitleTest 12 | { 13 | [TestMethod] 14 | public void MappingTitle() 15 | { 16 | var models = GetModels(); 17 | var bytes = ExcelHelper.ObjectToExcelBytes(models, options => 18 | { 19 | options.MappingColumnAction = (title, _) => 20 | { 21 | if (title == "姓名") return "n c name"; 22 | return title; 23 | }; 24 | }); 25 | Assert.IsNotNull(bytes); 26 | 27 | Assert.IsTrue(bytes.Length > 0); 28 | 29 | var importer = new ExcelImporter(); 30 | var result = importer.ExcelToObject>(bytes).ToList(); 31 | Console.WriteLine(JsonConvert.SerializeObject(result)); 32 | } 33 | 34 | private IEnumerable GetModels() 35 | { 36 | var list = new List 37 | { 38 | new() 39 | { 40 | Name = "Three Zhang", 41 | Age = 18, 42 | Birthday = new DateTime(1990, 1, 1), 43 | Birthday2 = null 44 | }, 45 | new() 46 | { 47 | Name = "Four Lee", 48 | Age = 18, 49 | Birthday = new DateTime(1990, 1, 1), 50 | Birthday2 = null 51 | } 52 | }; 53 | return list; 54 | } 55 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Chsword.Excel2Object.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | annotations 6 | false 7 | 8 | 9 | 10 | TRACE;DEBUG 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | PreserveNewest 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | PreserveNewest 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Chsword.Excel2Object/Options/FormulaColumnsCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Linq.Expressions; 3 | using Chsword.Excel2Object.Functions; 4 | 5 | namespace Chsword.Excel2Object.Options; 6 | 7 | public class FormulaColumnsCollection : ICollection 8 | { 9 | public List FormulaColumns { get; set; } = new(); 10 | public int Count => FormulaColumns.Count; 11 | 12 | public bool IsReadOnly => false; 13 | 14 | public void Add(FormulaColumn item) 15 | { 16 | if (item == null) throw new Excel2ObjectException("item must not null"); 17 | 18 | if (FormulaColumns.Any(c => c.Title == item.Title)) 19 | throw new Excel2ObjectException("same title has existed in options.FormulaColumns"); 20 | 21 | if (!string.IsNullOrWhiteSpace(item.AfterColumnTitle) && 22 | FormulaColumns.Any(c => c.AfterColumnTitle == item.AfterColumnTitle)) 23 | throw new Excel2ObjectException($"There is a formula column after {item.AfterColumnTitle} already."); 24 | 25 | FormulaColumns.Add(item); 26 | } 27 | 28 | public void Clear() 29 | { 30 | FormulaColumns.Clear(); 31 | } 32 | 33 | public bool Contains(FormulaColumn item) 34 | { 35 | return FormulaColumns.Contains(item); 36 | } 37 | 38 | public void CopyTo(FormulaColumn[] array, int arrayIndex) 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | 43 | public IEnumerator GetEnumerator() 44 | { 45 | return FormulaColumns.GetEnumerator(); 46 | } 47 | 48 | public bool Remove(FormulaColumn item) 49 | { 50 | return FormulaColumns.Remove(item); 51 | } 52 | 53 | IEnumerator IEnumerable.GetEnumerator() 54 | { 55 | return FormulaColumns.GetEnumerator(); 56 | } 57 | 58 | public void Add(string columnTitle, Expression> func) 59 | { 60 | FormulaColumns.Add(new FormulaColumn 61 | { 62 | Title = columnTitle, 63 | Formula = func 64 | }); 65 | } 66 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29609.76 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A7A41EB3-F1FD-40C1-87EB-BEC4B0D33810}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitattributes = .gitattributes 9 | .gitignore = .gitignore 10 | ExcelFunctions.md = ExcelFunctions.md 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chsword.Excel2Object", "Chsword.Excel2Object\Chsword.Excel2Object.csproj", "{ECED8DCD-6923-4F6A-8541-69C543A4F8D9}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chsword.Excel2Object.Tests", "Chsword.Excel2Object.Tests\Chsword.Excel2Object.Tests.csproj", "{96144AB9-652D-4A8F-A7DC-7E1438D72485}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {ECED8DCD-6923-4F6A-8541-69C543A4F8D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {ECED8DCD-6923-4F6A-8541-69C543A4F8D9}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {ECED8DCD-6923-4F6A-8541-69C543A4F8D9}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {ECED8DCD-6923-4F6A-8541-69C543A4F8D9}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {96144AB9-652D-4A8F-A7DC-7E1438D72485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {96144AB9-652D-4A8F-A7DC-7E1438D72485}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {96144AB9-652D-4A8F-A7DC-7E1438D72485}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {96144AB9-652D-4A8F-A7DC-7E1438D72485}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {514A78D0-5DEB-4FE5-9C1C-18242C10F709} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-ci.yml: -------------------------------------------------------------------------------- 1 | name: .NET CI 2 | 3 | on: 4 | push: 5 | branches: [ main, master ] 6 | pull_request: 7 | branches: [ main, master ] 8 | 9 | jobs: 10 | build-and-test: 11 | name: Build and Test 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, windows-latest, macos-latest] 16 | fail-fast: false 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | 22 | - name: Setup .NET SDK 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | dotnet-version: | 26 | 6.0.x 27 | 8.0.x 28 | 9.0.x 29 | 30 | - name: Cache NuGet packages 31 | uses: actions/cache@v4 32 | with: 33 | path: ~/.nuget/packages 34 | key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} 35 | restore-keys: | 36 | ${{ runner.os }}-nuget- 37 | 38 | - name: Restore dependencies 39 | run: dotnet restore 40 | 41 | - name: Build 42 | run: dotnet build --configuration Release --no-restore 43 | 44 | - name: Test 45 | run: dotnet test --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" 46 | 47 | - name: Upload test results 48 | if: always() 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: test-results-${{ matrix.os }} 52 | path: "**/test-results.trx" 53 | 54 | package: 55 | name: Create NuGet Package 56 | runs-on: ubuntu-latest 57 | needs: build-and-test 58 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 59 | 60 | steps: 61 | - name: Checkout code 62 | uses: actions/checkout@v4 63 | 64 | - name: Setup .NET SDK 65 | uses: actions/setup-dotnet@v4 66 | with: 67 | dotnet-version: | 68 | 6.0.x 69 | 8.0.x 70 | 9.0.x 71 | 72 | - name: Restore dependencies 73 | run: dotnet restore 74 | 75 | - name: Build 76 | run: dotnet build --configuration Release --no-restore 77 | 78 | - name: Pack 79 | run: dotnet pack --configuration Release --no-build --output ./artifacts 80 | 81 | - name: Upload NuGet package 82 | uses: actions/upload-artifact@v4 83 | with: 84 | name: nuget-package 85 | path: ./artifacts/*.nupkg 86 | -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Pr24NullableTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Chsword.Excel2Object.Tests.Models; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using Newtonsoft.Json; 7 | 8 | namespace Chsword.Excel2Object.Tests; 9 | 10 | [TestClass] 11 | public class Pr24NullableTest : BaseExcelTest 12 | { 13 | [TestMethod] 14 | public void ImportExcelNullableType() 15 | { 16 | var path = GetLocalFilePath("test.person.xlsx"); 17 | var importer = new ExcelImporter(); 18 | var result = importer.ExcelToObject(path)!.ToList(); 19 | Assert.AreEqual(2, result.Count); 20 | Console.WriteLine(JsonConvert.SerializeObject(result)); 21 | } 22 | 23 | [TestMethod] 24 | public void ImportExcelUnNullableTypeException() 25 | { 26 | var path = GetLocalFilePath("test.person.xlsx"); 27 | var importer = new ExcelImporter(); 28 | Assert.ThrowsException(() => 29 | { 30 | var result = importer.ExcelToObject(path)!.ToList(); 31 | Console.WriteLine(JsonConvert.SerializeObject(result)); 32 | }); 33 | } 34 | 35 | [TestMethod] 36 | public void ImportExcelUnNullableType() 37 | { 38 | var path = GetLocalFilePath("test.person.unnullable.xlsx"); 39 | var importer = new ExcelImporter(); 40 | var result = importer.ExcelToObject(path)!.ToList(); 41 | Assert.AreEqual(2, result.Count); 42 | Console.WriteLine(JsonConvert.SerializeObject(result)); 43 | } 44 | 45 | private List GetPersonList() 46 | { 47 | return new List 48 | { 49 | new() {Name = "张三", Age = 18, Birthday = null}, 50 | new() {Name = "李四", Age = null, Birthday = new DateTime(2021, 10, 10)} 51 | }; 52 | } 53 | 54 | [TestMethod] 55 | public void ExportExcelNullableType() 56 | { 57 | var personList = GetPersonList(); 58 | var bytes = ExcelHelper.ObjectToExcelBytes(personList); 59 | Assert.IsNotNull(bytes); 60 | Assert.IsTrue(bytes.Length > 0); 61 | var importer = new ExcelImporter(); 62 | var result = importer.ExcelToObject(bytes).ToList(); 63 | Console.WriteLine(JsonConvert.SerializeObject(result)); 64 | } 65 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Internal/ExcelUtil.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Reflection; 3 | 4 | namespace Chsword.Excel2Object.Internal; 5 | 6 | internal static class ExcelUtil 7 | { 8 | /// 9 | /// Get the ExcelTitleAttribute on class 10 | /// 11 | /// 12 | /// if there's not a ExcelTitleAttribute, will return null. 13 | public static ExcelTitleAttribute? GetClassExportAttribute() 14 | { 15 | var attrs = typeof(T).GetCustomAttributes(true); 16 | var attr = GetExcelTitleAttributeFromAttributes(attrs, 0); 17 | return attr; 18 | } 19 | 20 | /// 21 | /// Get the ExcelTitleAttribute on properties 22 | /// 23 | /// 24 | /// 25 | public static Dictionary GetPropertiesAttributesDict() 26 | { 27 | var dict = new Dictionary(); 28 | var defaultOrder = 10000; 29 | var props = typeof(T).GetTypeInfo().GetRuntimeProperties(); 30 | foreach (var propertyInfo in props) 31 | { 32 | var attrs = propertyInfo.GetCustomAttributes(true); 33 | var attr = GetExcelTitleAttributeFromAttributes(attrs, defaultOrder++); 34 | if (attr == null) continue; 35 | dict.Add(propertyInfo, attr); 36 | } 37 | 38 | return dict; 39 | } 40 | 41 | /// 42 | /// get ExcelTitleAttribute in Attributes 43 | /// 44 | /// 45 | /// 如果未设置Order则使用此Order 46 | /// 47 | private static ExcelTitleAttribute? GetExcelTitleAttributeFromAttributes(object[] attrs, int defaultOrder) 48 | { 49 | var attrTitle = attrs.FirstOrDefault(c => c is ExcelTitleAttribute or DisplayAttribute); 50 | if (attrTitle == null) return null; 51 | if (attrTitle is DisplayAttribute display) 52 | return new ExcelTitleAttribute(display.Name!) 53 | { 54 | Order = display.GetOrder() ?? defaultOrder 55 | }; 56 | 57 | var attrResult = attrTitle as ExcelTitleAttribute; 58 | if (attrResult?.Order == 0) attrResult.Order = defaultOrder; 59 | 60 | return attrResult; 61 | } 62 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Chsword.Excel2Object/Functions/ColumnValue.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable UnusedParameter.Global 2 | 3 | namespace Chsword.Excel2Object.Functions; 4 | 5 | // this type only used in the Expression 6 | public class ColumnValue 7 | { 8 | public static ColumnValue operator +(ColumnValue a, ColumnValue b) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public static ColumnValue operator &(ColumnValue a, ColumnValue b) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public static ColumnValue operator /(ColumnValue a, ColumnValue b) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | 23 | public static ColumnValue operator ==(ColumnValue a, ColumnValue b) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | public static explicit operator DateTime(ColumnValue operand) 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | 33 | public static explicit operator int(ColumnValue operand) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | 38 | public static ColumnValue operator >(ColumnValue a, ColumnValue b) 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | 43 | public static ColumnValue operator >=(ColumnValue a, ColumnValue b) 44 | { 45 | throw new NotImplementedException(); 46 | } 47 | 48 | public static implicit operator ColumnValue(int operand) 49 | { 50 | throw new NotImplementedException(); 51 | } 52 | 53 | public static implicit operator ColumnValue(float operand) 54 | { 55 | throw new NotImplementedException(); 56 | } 57 | 58 | public static implicit operator ColumnValue(double operand) 59 | { 60 | throw new NotImplementedException(); 61 | } 62 | 63 | public static implicit operator ColumnValue(string operand) 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | 68 | public static ColumnValue operator !=(ColumnValue a, ColumnValue b) 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | public static ColumnValue operator <(ColumnValue a, ColumnValue b) 74 | { 75 | throw new NotImplementedException(); 76 | } 77 | 78 | public static ColumnValue operator <=(ColumnValue a, ColumnValue b) 79 | { 80 | throw new NotImplementedException(); 81 | } 82 | 83 | public static ColumnValue operator *(ColumnValue a, ColumnValue b) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public static ColumnValue operator -(ColumnValue a, ColumnValue b) 89 | { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | public static ColumnValue operator -(ColumnValue a) 94 | { 95 | throw new NotImplementedException(); 96 | } 97 | 98 | public override bool Equals(object? obj) 99 | { 100 | throw new NotImplementedException(); 101 | } 102 | 103 | public override int GetHashCode() 104 | { 105 | throw new NotImplementedException(); 106 | } 107 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Pr28MultipleSheetTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Chsword.Excel2Object.Tests.Models; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Newtonsoft.Json; 6 | 7 | namespace Chsword.Excel2Object.Tests; 8 | 9 | [TestClass] 10 | public class Pr28MultipleSheetTest : BaseExcelTest 11 | { 12 | // Import 13 | 14 | [TestMethod] 15 | public void ImportMultipleSheet() 16 | { 17 | var path = GetLocalFilePath("test-pr28-multiples-heet.xlsx"); 18 | var importer = new ExcelImporter(); 19 | var resultFlat = importer.ExcelToObject(path, "Flat3Door")!.ToList(); 20 | Assert.AreEqual(3, resultFlat.Count); 21 | Assert.AreEqual("陈皮", resultFlat[0].Name); 22 | Console.WriteLine(JsonConvert.SerializeObject(resultFlat)); 23 | var resultUp = importer.ExcelToObject(path, "Up3Door")!.ToList(); 24 | Assert.AreEqual(3, resultUp.Count); 25 | Assert.AreEqual("张启山", resultUp[0].Name); 26 | Console.WriteLine(JsonConvert.SerializeObject(resultUp)); 27 | } 28 | 29 | [TestMethod] 30 | public void ImportMultipleSheetException() 31 | { 32 | Assert.ThrowsException(() => 33 | { 34 | var path = GetLocalFilePath("test-pr28-multiples-heet.xlsx"); 35 | var importer = new ExcelImporter(); 36 | var sheetName = "xxxxxxxxxxxxxxxxxxxx3Door"; 37 | var result = importer.ExcelToObject(path, sheetName)!.ToList(); 38 | Assert.AreEqual(3, result.Count); 39 | Assert.AreEqual("陈皮", result[0].Name); 40 | }); 41 | } 42 | 43 | // Export 44 | 45 | [TestMethod] 46 | public void ExportMultipleSheet() 47 | { 48 | // read source 49 | 50 | var path = GetLocalFilePath("test-pr28-multiples-heet.xlsx"); 51 | var flatModel = ExcelHelper.ExcelToObject(path, "Flat3Door")!.ToList(); 52 | var upModel = ExcelHelper.ExcelToObject(path, "Up3Door")!.ToList(); 53 | var downModel = ExcelHelper.ExcelToObject(path, "Down3Door")!.ToList(); 54 | // write bytes 55 | var bytes = ExcelHelper.ObjectToExcelBytes(upModel, sheetTitle: "Up3Door"); 56 | Assert.IsNotNull(bytes); 57 | // append 58 | bytes = ExcelHelper.AppendObjectToExcelBytes(bytes, flatModel, "Flat3Door"); 59 | Assert.IsNotNull(bytes); 60 | bytes = ExcelHelper.AppendObjectToExcelBytes(bytes, downModel, "Down3Door"); 61 | Assert.IsNotNull(bytes); 62 | // check 63 | var importer = new ExcelImporter(); 64 | var resultFlat = importer.ExcelToObject(bytes, "Flat3Door").ToList(); 65 | Assert.AreEqual(3, resultFlat.Count); 66 | Assert.AreEqual("陈皮", resultFlat[0].Name); 67 | Console.WriteLine(JsonConvert.SerializeObject(resultFlat)); 68 | var resultUp = importer.ExcelToObject(bytes, "Up3Door").ToList(); 69 | Assert.AreEqual(3, resultUp.Count); 70 | Assert.AreEqual("张启山", resultUp[0].Name); 71 | Console.WriteLine(JsonConvert.SerializeObject(resultUp)); 72 | } 73 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExpressionConvertTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using Chsword.Excel2Object.Functions; 5 | using Chsword.Excel2Object.Internal; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using static Chsword.Excel2Object.ExcelFunctions; 8 | 9 | namespace Chsword.Excel2Object.Tests; 10 | 11 | [TestClass] 12 | public class ExpressionConvertTests : BaseFunctionTest 13 | { 14 | [TestMethod] 15 | public void Column() 16 | { 17 | TestFunction(c => c["One"], "A4"); 18 | } 19 | 20 | [TestMethod] 21 | public void ColumnWithRow() 22 | { 23 | TestFunction(c => c["One", 1], "A1"); 24 | } 25 | 26 | [TestMethod] 27 | public void Date() 28 | { 29 | TestFunction(c => DateAndTime.Date(2020, 2, 2), "DATE(2020,2,2)"); 30 | } 31 | 32 | [TestMethod] 33 | public void DateDif() 34 | { 35 | TestFunction(c => DateAndTime.DateDif(c["One"], c["Two"], "YD"), "DATEDIF(A4,B4,\"YD\")"); 36 | } 37 | 38 | [TestMethod] 39 | public void Day() 40 | { 41 | Expression, object>> exp = c => DateTime.Now.Day; 42 | var convert = new ExpressionConvert(new string[] { }, 3); 43 | var ret = convert.Convert(exp); 44 | Assert.AreEqual("DAY(NOW())", ret); 45 | } 46 | 47 | [TestMethod] 48 | public void Days() 49 | { 50 | TestFunction(c => DateAndTime.Days(c["One"], c["Two"]), "DAYS(A4,B4)"); 51 | } 52 | 53 | [TestMethod] 54 | public void EDate() 55 | { 56 | Expression, object>> exp = c => DateTime.Now.AddMonths(3); 57 | var convert = new ExpressionConvert(new string[] { }, 3); 58 | var ret = convert.Convert(exp); 59 | Assert.AreEqual("EDATE(NOW(),3)", ret); 60 | } 61 | 62 | [TestMethod] 63 | public void EDateWithColumn() 64 | { 65 | Expression, object>> exp = c => 66 | ((DateTime) c["Date"]).AddMonths((int) c["Month"]); 67 | var convert = new ExpressionConvert(new[] {"Date", "Month"}, 3); 68 | var ret = convert.Convert(exp); 69 | Assert.AreEqual("EDATE(A4,B4)", ret); 70 | } 71 | 72 | [TestMethod] 73 | public void Month() 74 | { 75 | Expression, object>> exp = c => DateTime.Now.Month; 76 | var convert = new ExpressionConvert(new string[] { }, 3); 77 | var ret = convert.Convert(exp); 78 | Assert.AreEqual("MONTH(NOW())", ret); 79 | } 80 | 81 | [TestMethod] 82 | public void Now() 83 | { 84 | Expression, object>> exp = c => DateTime.Now; 85 | var convert = new ExpressionConvert(new string[] { }, 3); 86 | var ret = convert.Convert(exp); 87 | Assert.AreEqual("NOW()", ret); 88 | } 89 | 90 | [TestMethod] 91 | public void Year() 92 | { 93 | Expression, object>> exp = c => DateTime.Now.Year; 94 | var convert = new ExpressionConvert(new string[] { }, 3); 95 | var ret = convert.Convert(exp); 96 | Assert.AreEqual("YEAR(NOW())", ret); 97 | } 98 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at chsword@126.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Issue16FormulaTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Chsword.Excel2Object.Options; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Newtonsoft.Json; 8 | 9 | namespace Chsword.Excel2Object.Tests; 10 | 11 | /// 12 | /// for config and formula 13 | /// 14 | [TestClass] 15 | public class Issue16FormulaTest : BaseExcelTest 16 | { 17 | [TestMethod] 18 | public void FormulaColumnExport() 19 | { 20 | var list = new List> 21 | { 22 | new() 23 | { 24 | ["姓名"] = "吴老狗", ["Age"] = "19" 25 | //, ["BirthYear"] = null 26 | }, 27 | new() {["姓名"] = "老林", ["Age"] = "50"} 28 | }; 29 | var bytes = new ExcelExporter().ObjectToExcelBytes(list, options => 30 | { 31 | options.ExcelType = ExcelType.Xlsx; 32 | options.FormulaColumns.Add(new FormulaColumn 33 | { 34 | Title = "BirthYear", 35 | Formula = c => c["Age"] + DateTime.Now.Year, 36 | AfterColumnTitle = "姓名" 37 | }); 38 | options.FormulaColumns.Add(new FormulaColumn 39 | { 40 | Title = "当前时间", 41 | Formula = c => DateTime.Now, 42 | AfterColumnTitle = "Age", 43 | FormulaResultType = typeof(DateTime) 44 | }); 45 | }); 46 | Assert.IsNotNull(bytes); 47 | var path = GetFilePath("test.xlsx"); 48 | File.WriteAllBytes(path, bytes); 49 | var result = ExcelHelper.ExcelToObject>(bytes).ToList(); 50 | Console.WriteLine(JsonConvert.SerializeObject(result)); 51 | Assert.AreEqual( 52 | (DateTime.Now.Year + 19).ToString(), 53 | result[0]["BirthYear"] 54 | ); 55 | } 56 | 57 | [TestMethod] 58 | public void FormulaColumnExportCover() 59 | { 60 | var list = new List> 61 | { 62 | new() 63 | { 64 | ["姓名"] = "吴老狗", ["BirthYear"] = null 65 | }, 66 | new() {["姓名"] = "老林", ["BirthYear"] = null} 67 | }; 68 | var bytes = new ExcelExporter().ObjectToExcelBytes(list, options => 69 | { 70 | options.ExcelType = ExcelType.Xlsx; 71 | options.FormulaColumns.Add(new FormulaColumn 72 | { 73 | Title = "BirthYear", 74 | Formula = c => c["Age"] + DateTime.Now.Year, 75 | AfterColumnTitle = "姓名" 76 | }); 77 | options.FormulaColumns.Add(new FormulaColumn 78 | { 79 | Title = "Age", 80 | Formula = c => DateTime.Now.Year, 81 | AfterColumnTitle = "BirthYear" 82 | }); 83 | }); 84 | Assert.IsNotNull(bytes); 85 | var path = GetFilePath("test.xlsx"); 86 | File.WriteAllBytes(path, bytes); 87 | var result = ExcelHelper.ExcelToObject>(bytes).ToList(); 88 | Console.WriteLine(JsonConvert.SerializeObject(result)); 89 | Assert.AreEqual( 90 | (DateTime.Now.Year * 2).ToString(), 91 | result[0]["BirthYear"] 92 | ); 93 | } 94 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/Issue31SuperClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Chsword.Excel2Object.Internal; 5 | using Chsword.Excel2Object.Options; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Newtonsoft.Json; 8 | 9 | namespace Chsword.Excel2Object.Tests; 10 | 11 | [TestClass] 12 | public class Issue31SuperClass 13 | { 14 | [TestMethod] 15 | public void CheckModelA() 16 | { 17 | var excel = TypeConvert.ConvertObjectToExcelModel(GetExcel()!, 18 | new ExcelExporterOptions()); 19 | 20 | Assert.IsNotNull(excel); 21 | 22 | Assert.AreEqual(1, excel.Sheets?.Count); 23 | Assert.IsNotNull(excel.Sheets); 24 | Assert.AreEqual("SuperClass", excel.Sheets[0].Title); 25 | Console.WriteLine(JsonConvert.SerializeObject(excel)); 26 | 27 | Assert.IsTrue(excel.Sheets[0].Columns.Any(c => c.Title == "IdA")); 28 | Assert.IsTrue(excel.Sheets[0].Columns.Any(c => c.Title == "P1")); 29 | } 30 | 31 | [TestMethod] 32 | public void CheckModelB() 33 | { 34 | var excel = TypeConvert.ConvertObjectToExcelModel( 35 | GetExcel(), 36 | new ExcelExporterOptions()); 37 | Assert.IsNotNull(excel); 38 | Assert.IsNotNull(excel.Sheets); 39 | Assert.AreEqual(1, excel.Sheets.Count); 40 | Assert.AreEqual("SubClassB", excel.Sheets[0].Title); 41 | Console.WriteLine(JsonConvert.SerializeObject(excel)); 42 | 43 | Assert.IsTrue(excel.Sheets[0].Columns 44 | .Any(c => c.Title == "IdB")); 45 | Assert.IsTrue(excel.Sheets[0] 46 | .Columns.Any(c => c.Title == "P1")); 47 | } 48 | 49 | [TestMethod] 50 | public void SuperClassTest() 51 | { 52 | var export = new ExcelExporter(); 53 | var bytes = export.ObjectToExcelBytes(GetExcel()); 54 | var importer = new ExcelImporter(); 55 | Assert.IsNotNull(bytes); 56 | Console.WriteLine(bytes.Length); 57 | Assert.IsNotNull(importer); 58 | // var model = ExcelImporter. 59 | } 60 | 61 | private List GetExcel() where T : SuperClass 62 | { 63 | if (typeof(T).Name == "SubClassA") 64 | { 65 | var list = new List 66 | { 67 | new() {Id = 1, P = "x"}, 68 | new() {Id = 2, P = "x"} 69 | }; 70 | return (list as List)!; 71 | } 72 | 73 | if (typeof(T).Name == "SubClassB") 74 | { 75 | var list = new List 76 | { 77 | new() {Id = 11, P = "x"}, 78 | new() {Id = 12, P = "x"} 79 | }; 80 | return (list as List)!; 81 | } 82 | 83 | throw new Exception("not support"); 84 | } 85 | 86 | [ExcelTitle("SuperClass")] 87 | public abstract class SuperClass 88 | { 89 | [ExcelColumn("Id1")] public int Id { get; set; } 90 | 91 | // ReSharper disable once NotNullOrRequiredMemberIsNotInitialized 92 | [ExcelColumn("P1")] public string P { get; set; } 93 | } 94 | 95 | public class SubClassA : SuperClass 96 | { 97 | [ExcelColumn("IdA")] public new int Id { get; set; } 98 | } 99 | 100 | [ExcelTitle("SubClassB")] 101 | public class SubClassB : SuperClass 102 | { 103 | [ExcelColumn("IdB")] public new int Id { get; set; } 104 | } 105 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/ExcelColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | using Chsword.Excel2Object.Styles; 2 | 3 | namespace Chsword.Excel2Object; 4 | 5 | /// 6 | /// Attribute to define properties for Excel columns, including styles for cells and headers. 7 | /// 8 | [AttributeUsage(AttributeTargets.Property)] 9 | public class ExcelColumnAttribute : ExcelTitleAttribute, IExcelHeaderStyle, IExcelCellStyle 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The title of the column. 15 | public ExcelColumnAttribute(string title) : base(title) 16 | { 17 | } 18 | 19 | // Cell 20 | 21 | /// 22 | /// Gets or sets the horizontal alignment of the cell. 23 | /// 24 | public HorizontalAlignment CellAlignment { get; set; } 25 | 26 | /// 27 | /// Gets or sets a value indicating whether the cell text is bold. 28 | /// 29 | public bool CellBold { get; set; } 30 | 31 | /// 32 | /// Gets or sets the font color of the cell. 33 | /// 34 | public ExcelStyleColor CellFontColor { get; set; } 35 | 36 | /// 37 | /// Gets or sets the font family of the cell. 38 | /// 39 | public string? CellFontFamily { get; set; } 40 | 41 | /// 42 | /// Gets or sets the font height of the cell. 43 | /// 44 | public double CellFontHeight { get; set; } 45 | 46 | /// 47 | /// Gets or sets a value indicating whether the cell text is italic. 48 | /// 49 | public bool CellItalic { get; set; } 50 | 51 | /// 52 | /// Gets or sets a value indicating whether the cell text has a strikeout. 53 | /// 54 | public bool CellStrikeout { get; set; } 55 | 56 | /// 57 | /// Gets or sets a value indicating whether the cell text is underlined. 58 | /// 59 | public bool CellUnderline { get; set; } 60 | 61 | /// 62 | /// Gets or sets the format of the cell. 63 | /// 64 | public string? Format { get; set; } 65 | 66 | // Header 67 | 68 | /// 69 | /// Gets or sets the horizontal alignment of the header. 70 | /// 71 | public HorizontalAlignment HeaderAlignment { get; set; } 72 | 73 | /// 74 | /// Gets or sets a value indicating whether the header text is bold. 75 | /// 76 | public bool HeaderBold { get; set; } 77 | 78 | /// 79 | /// Gets or sets the font color of the header. 80 | /// 81 | public ExcelStyleColor HeaderFontColor { get; set; } 82 | 83 | /// 84 | /// Gets or sets the font family of the header. 85 | /// 86 | public string? HeaderFontFamily { get; set; } 87 | 88 | /// 89 | /// Gets or sets the font height of the header. 90 | /// 91 | public double HeaderFontHeight { get; set; } 92 | 93 | /// 94 | /// Gets or sets a value indicating whether the header text is italic. 95 | /// 96 | public bool HeaderItalic { get; set; } 97 | 98 | /// 99 | /// Gets or sets a value indicating whether the header text has a strikeout. 100 | /// 101 | public bool HeaderStrikeout { get; set; } 102 | 103 | /// 104 | /// Gets or sets a value indicating whether the header text is underlined. 105 | /// 106 | public bool HeaderUnderline { get; set; } 107 | } 108 | -------------------------------------------------------------------------------- /ExcelFunctions.md: -------------------------------------------------------------------------------- 1 | ### Use formula 2 | 3 | ``` csharp 4 | var bytes = new ExcelExporter().ObjectToExcelBytes(list, options => 5 | { 6 | options.ExcelType = ExcelType.Xlsx; 7 | options.FormulaColumns.Add(new FormulaColumn 8 | { 9 | Title = "BirthYear", 10 | Formula = c => (int) c["Age"] + DateTime.Now.Year, 11 | AfterColumnTitle = "Column1" 12 | }); 13 | }); 14 | // c => (int) c["Age"] + DateTime.Now.Year will convert to like =A3+YEAR(NOW()) 15 | ``` 16 | 17 | ### Base 18 | 19 | |Function|Syntax |Description| 20 | |---|:---|:----| 21 | |A4 | ``` c => c["One"] ```| Cell in current row| 22 | |A2 | ``` c => c["One",2] ```| Specify any Cell| 23 | |A2:B4 | ``` c => c.Matrix("One",2,"Two",4) ```| 24 | 25 | 26 | ### Math Functions 27 | 28 | |Function|Syntax |Description| 29 | |:---|:---|:----| 30 | |ABS | ``` c => ExcelFunctions.Math.Abs(c["One"]) ```| 31 | |PI|``` c => ExcelFunctions.Math.PI() ```| 32 | 33 | ### Statistics Functions 34 | 35 | |Function|Syntax |Description| 36 | |:---|:---|:----| 37 | |SUM | ``` c => ExcelFunctions.Statistics.Sum(c.Matix("One", 1, "Two", 2), c.Matrix("Six", 11, "Five", 2)) ```|SUM(A1:B2,F11:E2)| 38 | 39 | ### Condition Functions 40 | |Function|Syntax |Description| 41 | |:---|:---|:----| 42 | |IF | ``` c => ExcelFunctions.Statistics.Sum(c.Matix("One", 1, "Two", 2), c.Matrix("Six", 11, "Five", 2)) ```|SUM(A1:B2,F11:E2)| 43 | 44 | ### Reference Functions 45 | |Function|Syntax |Description| 46 | |:---|:---|:----| 47 | |LOOKUP | ``` c => ExcelFunctions.Reference.Lookup(4.19,c.Matrix("One", 2, "One", 6),c.Matrix("Two", 2, "Two", 6)) ```|LOOKUP(4.19, A2:A6, B2:B6)| 48 | |VLOOKUP | ```c => ExcelFunctions.Reference.VLookup(c["One"], c.Matrix("One", 10, "Three", 20), 2, true)```| VLOOKUP(A4,A10:C20,2,TRUE)| 49 | |MATCH| 50 | |CHOOSE| 51 | |INDEX| 52 | 53 | ### Date and Time Functions 54 | |Function|Syntax |Description | 55 | |:---|:---|:-----| 56 | |DATE | | 57 | |DATEDIF | | 58 | |DAYS|| 59 | 60 | ### Text Functions 61 | |Function|Syntax |Description| 62 | |---|:---|:------| 63 | |FIND | ```c => ExcelFunctions.Text.Find("M",c["One",2]) ```|FIND("M",A2) | 64 | |ASC | | 65 | 66 | ### Symbol 67 | 68 | |Symbol|Mode|Synatx|Supported|Description| 69 | |---|---|---|:----|:----| 70 | |-|Unary|```c=>-c["One"]```|Yes | 71 | |+|Binary|```c=>c["One"]+c["Two"]```| Yes | 72 | |-|Binary|| Yes | 73 | |\*|Binary|| Yes | 74 | |/|Binary|| Yes | 75 | |%|Binary | 76 | |\^|Binary | 77 | |=|Binary|```c=>c["One"]==c["Two"]```| Yes | 78 | |\<\> | Binary|```c=>c["One"]!=c["Two"]```| Yes | 79 | |\>|Binary|| Yes | 80 | |\<|Binary|| Yes | 81 | |\>=|Binary|| Yes | 82 | |\<=|Binary|| Yes | 83 | |&|Binary|```c=>c["One"]&c["Two"]```|Yes|Join the string | 84 | |:|Binary|```c => c.Matrix("One", 1, "Two", 2)```|Yes|A1:B2 | 85 | |,|Binary|||use params Array | 86 | |Space|Binary | 87 | 88 | #### Reference 89 | 90 | - All Functions | https://support.office.com/en-us/article/excel-functions-by-category-5f91f4e9-7b42-46d2-9bd1-63f26a86c0eb?ui=en-US&rs=en-US&ad=US 91 | -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExpressionConvertSymbolTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace Chsword.Excel2Object.Tests; 4 | 5 | [TestClass] 6 | public class ExpressionConvertSymbolTests : BaseFunctionTest 7 | { 8 | [TestMethod] 9 | public void Addition() 10 | { 11 | TestFunction(c => c["One"] + c["Two"], "A4+B4"); 12 | TestFunction(c => c["One"] + 1, "A4+1"); 13 | TestFunction(c => 11 + c["Two"], "11+B4"); 14 | TestFunction(c => 1 + 2, "3"); 15 | } 16 | 17 | [TestMethod] 18 | public void Colon() 19 | { 20 | TestFunction(c => c.Matrix("One", 1, "Two", 2), "A1:B2"); 21 | } 22 | 23 | [TestMethod] 24 | public void Division() 25 | { 26 | TestFunction(c => c["One"] / c["Two"], "A4/B4"); 27 | TestFunction(c => c["One"] / 1, "A4/1"); 28 | TestFunction(c => 11 / c["Two"], "11/B4"); 29 | TestFunction(c => 1 / 2, "0"); 30 | } 31 | 32 | [TestMethod] 33 | public void Equal() 34 | { 35 | TestFunction(c => c["One"] == c["Two"], "A4=B4"); 36 | TestFunction(c => c["One"] == 1, "A4=1"); 37 | TestFunction(c => 11 == c["Two"], "11=B4"); 38 | // TestFunction(c => 1 == 2, ""); 39 | } 40 | 41 | [TestMethod] 42 | public void GreaterThan() 43 | { 44 | TestFunction(c => c["One"] > c["Two"], "A4>B4"); 45 | TestFunction(c => c["One"] > 1, "A4>1"); 46 | TestFunction(c => 11 > c["Two"], "11>B4"); 47 | // TestFunction(c => 1 == 2, ""); 48 | } 49 | 50 | [TestMethod] 51 | public void GreaterThanOrEqual() 52 | { 53 | TestFunction(c => c["One"] >= c["Two"], "A4>=B4"); 54 | TestFunction(c => c["One"] >= 1, "A4>=1"); 55 | TestFunction(c => 11 >= c["Two"], "11>=B4"); 56 | // TestFunction(c => 1 == 2, ""); 57 | } 58 | 59 | [TestMethod] 60 | public void LessThan() 61 | { 62 | TestFunction(c => c["One"] < c["Two"], "A4 c["One"] < 1, "A4<1"); 64 | TestFunction(c => 11 < c["Two"], "11 1 == 2, ""); 66 | } 67 | 68 | [TestMethod] 69 | public void LessThanOrEqual() 70 | { 71 | TestFunction(c => c["One"] <= c["Two"], "A4<=B4"); 72 | TestFunction(c => c["One"] <= 1, "A4<=1"); 73 | TestFunction(c => 11 <= c["Two"], "11<=B4"); 74 | // TestFunction(c => 1 == 2, ""); 75 | } 76 | 77 | [TestMethod] 78 | public void Multiplication() 79 | { 80 | TestFunction(c => c["One"] * c["Two"], "A4*B4"); 81 | TestFunction(c => c["One"] * 1, "A4*1"); 82 | TestFunction(c => 11 * c["Two"], "11*B4"); 83 | TestFunction(c => 1 * 2, "2"); 84 | } 85 | 86 | [TestMethod] 87 | public void Negative() 88 | { 89 | TestFunction(c => -c["Two"], "-B4"); 90 | TestFunction(c => -2, "-2"); 91 | } 92 | 93 | [TestMethod] 94 | public void NotEqual() 95 | { 96 | TestFunction(c => c["One"] != c["Two"], "A4<>B4"); 97 | TestFunction(c => c["One"] != 1, "A4<>1"); 98 | TestFunction(c => 11 != c["Two"], "11<>B4"); 99 | // TestFunction(c => 1 == 2, ""); 100 | } 101 | 102 | [TestMethod] 103 | public void StringJoin() 104 | { 105 | TestFunction(c => c["One"] & c["Two"], "A4&B4"); 106 | TestFunction(c => c["One"] & "1", "A4&\"1\""); 107 | TestFunction(c => "11" & c["Two"], "\"11\"&B4"); 108 | } 109 | 110 | [TestMethod] 111 | public void Subtraction() 112 | { 113 | TestFunction(c => c["One"] - c["Two"], "A4-B4"); 114 | TestFunction(c => c["One"] - 1, "A4-1"); 115 | TestFunction(c => 11 - c["Two"], "11-B4"); 116 | TestFunction(c => 1 - 2, "-1"); 117 | } 118 | } -------------------------------------------------------------------------------- /.github/QUICK_REFERENCE.md: -------------------------------------------------------------------------------- 1 | # 发布快速参考卡 / Release Quick Reference Card 2 | 3 | ## 🚀 快速发布步骤 / Quick Release Steps 4 | 5 | ```bash 6 | # 1️⃣ 更新版本号 / Update Version 7 | vim Chsword.Excel2Object/Chsword.Excel2Object.csproj 8 | # 修改 X.Y.Z 9 | 10 | # 2️⃣ 更新发布说明 / Update Release Notes 11 | vim README.md README_EN.md 12 | # 添加版本发布说明 13 | 14 | # 3️⃣ 提交变更 / Commit Changes 15 | git add . 16 | git commit -m "chore: bump version to X.Y.Z" 17 | git push origin main 18 | 19 | # 4️⃣ 创建并推送 Tag / Create & Push Tag 20 | git tag vX.Y.Z 21 | git push origin vX.Y.Z 22 | 23 | # 5️⃣ 完成!/ Done! 24 | # 访问 GitHub Actions 监控进度 25 | # https://github.com/chsword/Excel2Object/actions 26 | ``` 27 | 28 | ## 📊 版本递增速查表 / Version Increment Quick Reference 29 | 30 | | 变更类型 | 版本递增 | 示例 | 31 | |---------|---------|------| 32 | | 🐛 Bug 修复 | `X.Y.Z → X.Y.(Z+1)` | 2.0.2 → 2.0.3 | 33 | | ⚡ 性能优化 | `X.Y.Z → X.Y.(Z+1)` | 2.0.3 → 2.0.4 | 34 | | ✨ 新功能 | `X.Y.Z → X.(Y+1).0` | 2.0.4 → 2.1.0 | 35 | | 🎯 新框架支持 | `X.Y.Z → X.(Y+1).0` | 2.1.0 → 2.2.0 | 36 | | 💥 破坏性变更 | `X.Y.Z → (X+1).0.0` | 2.2.0 → 3.0.0 | 37 | 38 | ## ⚙️ 自动化流程检查点 / Automation Checkpoints 39 | 40 | 自动发布流程会执行以下检查: 41 | 42 | - ✅ **版本格式验证**: 确保符合 SemVer 规范 43 | - ✅ **版本一致性**: Tag 版本 = csproj 版本 44 | - ✅ **多平台构建**: Ubuntu + Windows 45 | - ✅ **单元测试**: 所有测试必须通过 46 | - ✅ **NuGet 打包**: 生成多框架包 47 | - ✅ **自动发布**: NuGet.org + GitHub Release 48 | 49 | ## 🔧 必需配置 / Required Configuration 50 | 51 | ### GitHub Secrets 52 | ``` 53 | NUGET_API_KEY - NuGet.org API 密钥 54 | ``` 55 | 56 | **获取方式 / How to Get**: 57 | 1. 登录 https://www.nuget.org 58 | 2. Account Settings → API Keys 59 | 3. Create → 复制密钥 60 | 4. GitHub 仓库 → Settings → Secrets → New secret 61 | 62 | ## 📝 发布说明模板 / Release Notes Template 63 | 64 | ### 中文版本 65 | ```markdown 66 | * **YYYY.MM.DD** - vX.Y.Z 67 | - [x] ✨ **新增:** 功能描述 68 | - [x] 🐛 **修复:** 问题描述 69 | - [x] ⚡ **优化:** 改进描述 70 | - [x] 📝 **文档:** 文档更新 71 | ``` 72 | 73 | ### 英文版本 74 | ```markdown 75 | * **YYYY.MM.DD** - vX.Y.Z 76 | - [x] ✨ **Added:** Feature description 77 | - [x] 🐛 **Fixed:** Issue description 78 | - [x] ⚡ **Improved:** Enhancement description 79 | - [x] 📝 **Docs:** Documentation update 80 | ``` 81 | 82 | ## 🚨 常见问题快速解决 / Quick Troubleshooting 83 | 84 | ### 问题 1: 版本号不匹配 85 | ``` 86 | 错误: Tag version (2.0.3) does not match csproj version (2.0.2) 87 | 88 | 解决: 确保 .csproj 文件中的 与 tag 一致 89 | ``` 90 | 91 | ### 问题 2: NuGet 推送失败 92 | ``` 93 | 错误: 401 Unauthorized 或 409 Conflict 94 | 95 | 解决: 96 | - 检查 NUGET_API_KEY 是否正确 97 | - 确认版本号未被占用 98 | ``` 99 | 100 | ### 问题 3: 测试失败 101 | ``` 102 | 错误: Tests failed 103 | 104 | 解决: 105 | 1. 本地运行测试: dotnet test 106 | 2. 修复失败的测试 107 | 3. 删除 tag: git push origin :refs/tags/vX.Y.Z 108 | 4. 重新发布 109 | ``` 110 | 111 | ## 📦 预发布版本 / Pre-release Versions 112 | 113 | ```bash 114 | # Alpha 版本 115 | git tag v2.1.0-alpha.1 116 | git push origin v2.1.0-alpha.1 117 | 118 | # Beta 版本 119 | git tag v2.1.0-beta.1 120 | git push origin v2.1.0-beta.1 121 | 122 | # RC 版本 123 | git tag v2.1.0-rc.1 124 | git push origin v2.1.0-rc.1 125 | ``` 126 | 127 | ## 🔗 重要链接 / Important Links 128 | 129 | - 📚 [完整发布指南](../.github/RELEASE_GUIDE.md) 130 | - 📋 [版本管理规范](../.github/VERSIONING.md) 131 | - 🤖 [Copilot 指令](../.github/copilot-instructions.md) 132 | - 🔄 [发布自动化总览](../RELEASE_AUTOMATION.md) 133 | - 🏃 [CI Workflow](../..github/workflows/dotnet-ci.yml) 134 | - 🚀 [Release Workflow](../..github/workflows/release.yml) 135 | 136 | ## ⏱️ 预计时间 / Estimated Time 137 | 138 | | 步骤 | 时间 | 139 | |-----|------| 140 | | 准备阶段(更新版本和文档)| 5-15 分钟 | 141 | | 创建并推送 Tag | < 1 分钟 | 142 | | 自动化流程执行 | 5-10 分钟 | 143 | | **总计** | **10-26 分钟** | 144 | 145 | ## 📞 获取帮助 / Get Help 146 | 147 | 遇到问题? 148 | 149 | - 💬 [GitHub Discussions](https://github.com/chsword/Excel2Object/discussions) 150 | - 🐛 [Issue Tracker](https://github.com/chsword/Excel2Object/issues) 151 | - 📧 Email: chsword@126.com 152 | 153 | --- 154 | 155 | **提示**: 将此页面加入书签以便快速查阅! 156 | 157 | **Tip**: Bookmark this page for quick reference! 158 | -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/SimpleAutoWidthTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Chsword.Excel2Object.Tests.Models; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Chsword.Excel2Object.Tests; 9 | 10 | /// 11 | /// 简单的功能验证程序,用于测试自动列宽功能 12 | /// 13 | [TestClass] 14 | public class SimpleAutoWidthTest 15 | { 16 | [TestMethod] 17 | public void BasicAutoWidthTest() 18 | { 19 | // 准备测试数据 20 | var testData = new List> 21 | { 22 | new() { ["Name"] = "张三", ["Age"] = 25, ["Description"] = "Short" }, 23 | new() { ["Name"] = "李四有一个很长的名字", ["Age"] = 30, ["Description"] = "This is a much longer description text" }, 24 | new() { ["Name"] = "王五", ["Age"] = 35, ["Description"] = "Medium length text" } 25 | }; 26 | 27 | // 测试自动列宽 28 | var bytesAuto = ExcelHelper.ObjectToExcelBytes(testData, options => 29 | { 30 | options.ExcelType = ExcelType.Xlsx; 31 | options.AutoColumnWidth = true; 32 | options.MinColumnWidth = 8; 33 | options.MaxColumnWidth = 30; 34 | }); 35 | 36 | // 测试固定列宽 37 | var bytesFixed = ExcelHelper.ObjectToExcelBytes(testData, options => 38 | { 39 | options.ExcelType = ExcelType.Xlsx; 40 | options.AutoColumnWidth = false; 41 | options.DefaultColumnWidth = 15; 42 | }); 43 | 44 | // 验证结果 45 | Assert.IsNotNull(bytesAuto, "自动列宽导出失败"); 46 | Assert.IsNotNull(bytesFixed, "固定列宽导出失败"); 47 | Assert.IsTrue(bytesAuto.Length > 0, "自动列宽文件为空"); 48 | Assert.IsTrue(bytesFixed.Length > 0, "固定列宽文件为空"); 49 | 50 | // 验证导入功能 51 | var importer = new ExcelImporter(); 52 | var resultAuto = importer.ExcelToObject>(bytesAuto).ToList(); 53 | var resultFixed = importer.ExcelToObject>(bytesFixed).ToList(); 54 | 55 | Assert.AreEqual(3, resultAuto.Count, "自动列宽导入数据数量不正确"); 56 | Assert.AreEqual(3, resultFixed.Count, "固定列宽导入数据数量不正确"); 57 | 58 | // 验证数据内容 59 | Assert.AreEqual("张三", resultAuto[0]["Name"].ToString(), "自动列宽数据内容不正确"); 60 | Assert.AreEqual("张三", resultFixed[0]["Name"].ToString(), "固定列宽数据内容不正确"); 61 | 62 | Console.WriteLine("✅ 自动列宽功能测试通过"); 63 | Console.WriteLine($"自动列宽文件大小: {bytesAuto.Length} bytes"); 64 | Console.WriteLine($"固定列宽文件大小: {bytesFixed.Length} bytes"); 65 | } 66 | 67 | [TestMethod] 68 | public void TestColumnWidthCalculation() 69 | { 70 | // 测试列宽计算逻辑 71 | var testCases = new[] 72 | { 73 | new { Text = "A", ExpectedMin = 3 }, // 最小宽度 (1字符 + 2填充) 74 | new { Text = "Hello", ExpectedMin = 7 }, // 5字符 + 2填充 75 | new { Text = "中文", ExpectedMin = 6 }, // 2个中文字符(4) + 2填充 76 | new { Text = "Mixed中文", ExpectedMin = 11 }, // Mixed(5) + 中文(4) + 2填充 77 | new { Text = "", ExpectedMin = 1 } // 空字符串最小为1 78 | }; 79 | 80 | foreach (var testCase in testCases) 81 | { 82 | // 这里我们无法直接测试私有方法,但可以通过导出验证 83 | var data = new List> 84 | { 85 | new() { ["TestColumn"] = testCase.Text } 86 | }; 87 | 88 | var bytes = ExcelHelper.ObjectToExcelBytes(data, options => 89 | { 90 | options.ExcelType = ExcelType.Xlsx; 91 | options.AutoColumnWidth = true; 92 | options.MinColumnWidth = 1; // 允许最小宽度 93 | options.MaxColumnWidth = 100; // 允许最大宽度 94 | }); 95 | 96 | Assert.IsNotNull(bytes, $"列宽计算测试失败: {testCase.Text}"); 97 | } 98 | 99 | Console.WriteLine("✅ 列宽计算逻辑测试通过"); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Chsword.Excel2Object/Internal/ExcelConstants.cs: -------------------------------------------------------------------------------- 1 | namespace Chsword.Excel2Object.Internal; 2 | 3 | internal static class ExcelConstants 4 | { 5 | public const int DefaultColumnWidthMultiplier = 256; 6 | public const int DefaultHeaderRowIndex = 0; 7 | public const int DefaultDataStartRowIndex = 1; 8 | public const short DefaultFontHeightInPoints = 10; 9 | 10 | public static class CellTypes 11 | { 12 | public const string Text = "text"; 13 | public const string DateTime = "datetime"; 14 | public const string Number = "number"; 15 | } 16 | 17 | public static class BooleanValues 18 | { 19 | public static readonly string[] TrueValues = { "1", "是", "yes", "true" }; 20 | public static readonly string[] FalseValues = { "0", "否", "no", "false" }; 21 | } 22 | 23 | public static class DateFormats 24 | { 25 | public const string YearSuffix = "年"; 26 | public const string MonthSuffix = "月"; 27 | public const string DaySuffix = "日"; 28 | public const string DefaultYearMonthSuffix = "-01-01"; 29 | public const string DefaultDaySuffix = "-01"; 30 | 31 | /// 32 | /// Provides a comprehensive list of common date and time format patterns used for parsing date/time strings from Excel cells. 33 | /// 34 | /// 35 | /// The formats are organized into groups, including: 36 | /// 37 | /// ISO 8601 formats (e.g., "yyyy-MM-ddTHH:mm:ss") 38 | /// Date with various separators (e.g., "yyyy/MM/dd", "dd-MM-yyyy") 39 | /// Date with time (12-hour and 24-hour formats, with or without AM/PM) 40 | /// Short date formats (single digit month/day) 41 | /// Time only formats (24-hour and 12-hour with AM/PM) 42 | /// 43 | /// This array is intended for use when attempting to parse date/time values from Excel cells that may be formatted in a variety of ways. 44 | /// 45 | public static readonly string[] CommonDateTimeFormats = 46 | [ 47 | // ISO 8601 formats 48 | "yyyy-MM-ddTHH:mm:ss", 49 | "yyyy-MM-ddTHH:mm:ssZ", 50 | "yyyy-MM-ddTHH:mm:ss.fff", 51 | "yyyy-MM-ddTHH:mm:ss.fffZ", 52 | "yyyy-MM-dd HH:mm:ss", 53 | "yyyy-MM-dd HH:mm", 54 | 55 | // Date with various separators 56 | "yyyy-MM-dd", 57 | "yyyy/MM/dd", 58 | "yyyy.MM.dd", 59 | "dd-MM-yyyy", 60 | "dd/MM/yyyy", 61 | "dd.MM.yyyy", 62 | "MM-dd-yyyy", 63 | "MM/dd/yyyy", 64 | "MM.dd.yyyy", 65 | 66 | // Date with time (12-hour format) 67 | "yyyy-MM-dd hh:mm:ss tt", 68 | "yyyy-MM-dd hh:mm tt", 69 | "dd-MM-yyyy hh:mm:ss tt", 70 | "dd/MM/yyyy hh:mm:ss tt", 71 | "MM-dd-yyyy hh:mm:ss tt", 72 | "MM/dd/yyyy hh:mm:ss tt", 73 | 74 | // Date with time (24-hour format) 75 | "dd-MM-yyyy HH:mm:ss", 76 | "dd/MM/yyyy HH:mm:ss", 77 | "MM-dd-yyyy HH:mm:ss", 78 | "MM/dd/yyyy HH:mm:ss", 79 | "dd-MM-yyyy HH:mm", 80 | "dd/MM/yyyy HH:mm", 81 | "MM-dd-yyyy HH:mm", 82 | "MM/dd/yyyy HH:mm", 83 | 84 | // Short date formats (with single digit month/day) 85 | "yyyy/M/d", 86 | "yyyy-M-d", 87 | "d/M/yyyy", 88 | "d-M-yyyy", 89 | "M/d/yyyy", 90 | "M-d-yyyy", 91 | 92 | // Time only formats (24-hour) 93 | "HH:mm:ss", 94 | "HH:mm", 95 | "H:mm:ss", 96 | "H:mm", 97 | 98 | // Time only formats (12-hour with AM/PM) 99 | "hh:mm:ss tt", 100 | "hh:mm tt", 101 | "h:mm:ss tt", 102 | "h:mm tt" 103 | ]; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Chsword.Excel2Object/ExcelHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using Chsword.Excel2Object.Options; 3 | 4 | namespace Chsword.Excel2Object; 5 | 6 | public static class ExcelHelper 7 | { 8 | public static byte[]? AppendObjectToExcelBytes(byte[] sourceExcelBytes, IEnumerable data, 9 | string sheetTitle) 10 | { 11 | var excelExporter = new ExcelExporter(); 12 | return excelExporter.AppendObjectToExcelBytes(sourceExcelBytes, data, sheetTitle); 13 | } 14 | 15 | /// 16 | /// convert a excel file(bytes) to IEnumerable of TModel 17 | /// 18 | /// 19 | /// the excel file bytes 20 | /// specify sheet name which wants to import 21 | /// 22 | public static IEnumerable ExcelToObject(byte[] bytes, string? sheetTitle = null) 23 | where TModel : class, new() 24 | { 25 | var importer = new ExcelImporter(); 26 | return importer.ExcelToObject(bytes, sheetTitle); 27 | } 28 | 29 | /// 30 | /// import file excel file to a IEnumerable of TModel 31 | /// 32 | /// 33 | /// excel full path 34 | /// specify sheet name which wants to import 35 | /// 36 | public static IEnumerable? ExcelToObject(string path, string? sheetTitle = null) 37 | where TModel : class, new() 38 | { 39 | var importer = new ExcelImporter(); 40 | return importer.ExcelToObject(path, sheetTitle); 41 | } 42 | 43 | /// 44 | /// Export object to excel file 45 | /// 46 | /// 47 | /// a IEnumerable of TModel 48 | /// excel full path 49 | public static void ObjectToExcel(IEnumerable data, string path) where TModel : class, new() 50 | { 51 | var excelExporter = new ExcelExporter(); 52 | var bytes = excelExporter.ObjectToExcelBytes(data); 53 | WriteExcelBytesToFile(bytes, path); 54 | } 55 | 56 | /// 57 | /// Export object to excel file 58 | /// 59 | /// 60 | /// a IEnumerable of TModel 61 | /// excel full path 62 | /// 63 | public static void ObjectToExcel(IEnumerable data, string path, ExcelType excelType) 64 | where TModel : class, new() 65 | { 66 | var excelExporter = new ExcelExporter(); 67 | var bytes = excelExporter.ObjectToExcelBytes(data, excelType); 68 | WriteExcelBytesToFile(bytes, path); 69 | } 70 | 71 | /// 72 | /// Export object to excel bytes 73 | /// 74 | /// 75 | /// 76 | /// 77 | /// 78 | public static byte[]? ObjectToExcelBytes(IEnumerable data, 79 | ExcelType excelType = ExcelType.Xls, 80 | string? sheetTitle = null) 81 | where TModel : class, new() 82 | { 83 | var excelExporter = new ExcelExporter(); 84 | return excelExporter.ObjectToExcelBytes(data, excelType, sheetTitle); 85 | } 86 | 87 | public static byte[]? ObjectToExcelBytes(DataTable dt, ExcelType excelType, 88 | string? sheetTitle = null) 89 | { 90 | var excelExporter = new ExcelExporter(); 91 | return excelExporter.ObjectToExcelBytes(dt, excelType, sheetTitle); 92 | } 93 | 94 | public static byte[]? ObjectToExcelBytes(IEnumerable data, 95 | Action optionsAction) 96 | { 97 | var excelExporter = new ExcelExporter(); 98 | return excelExporter.ObjectToExcelBytes(data, optionsAction); 99 | } 100 | 101 | private static void WriteExcelBytesToFile(byte[]? bytes, string path) 102 | { 103 | if (bytes != null) 104 | File.WriteAllBytes(path, bytes); 105 | } 106 | } -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # GitHub Copilot 使用说明 2 | 3 | 本文档为 Excel2Object 项目提供 GitHub Copilot 的使用指导和项目规范说明。 4 | 5 | ## 项目简介 6 | 7 | Excel2Object 是一个用于 Excel 与 .NET 对象互相转换的类库。该项目支持多个 .NET 框架版本,包括 .NET Framework 4.7.2+、.NET Standard 2.0/2.1、.NET 6.0/8.0/9.0。 8 | 9 | ## 核心功能 10 | 11 | 1. **Excel 转对象**: 将 Excel 文件内容转换为 .NET 对象集合 12 | 2. **对象转 Excel**: 将 .NET 对象集合导出为 Excel 文件 13 | 3. **自动列宽**: 基于内容自动调整 Excel 列宽 14 | 4. **日期时间格式**: 支持 56 种日期时间格式 15 | 5. **样式支持**: 支持字体、颜色、对齐方式等 Excel 样式设置 16 | 17 | ## 技术栈 18 | 19 | - **主要依赖**: NPOI 2.7.5 (Excel 操作), SixLabors.ImageSharp 3.1.11 (图像处理) 20 | - **目标框架**: 多目标框架 (net472, netstandard2.0, netstandard2.1, net6.0, net8.0, net9.0) 21 | - **测试框架**: xUnit 22 | - **CI/CD**: GitHub Actions 23 | 24 | ## 编码规范 25 | 26 | ### 命名约定 27 | 28 | 1. **类名**: 使用 PascalCase,例如 `ExcelExporter`, `ExcelImporter` 29 | 2. **方法名**: 使用 PascalCase,例如 `ToExcel()`, `ToObject()` 30 | 3. **属性名**: 使用 PascalCase,例如 `FileName`, `SheetName` 31 | 4. **私有字段**: 使用 camelCase,例如 `_workbook`, `_sheet` 32 | 5. **常量**: 使用 UPPER_CASE,例如 `MAX_COLUMN_WIDTH` 33 | 34 | ### 代码风格 35 | 36 | 1. **缩进**: 使用 Tab 缩进(项目现有风格) 37 | 2. **语言版本**: C# 12.0,启用隐式 using 38 | 3. **可空性**: 启用可空引用类型注解 (`Nullable=annotations`) 39 | 4. **注释**: 对公共 API 提供 XML 文档注释 40 | 5. **异常处理**: 使用明确的异常类型,提供有意义的错误消息 41 | 42 | ### 特性使用 43 | 44 | 项目广泛使用 Attribute 来配置 Excel 映射: 45 | 46 | ```csharp 47 | [ExcelTitle("工作表名称")] 48 | public class MyModel 49 | { 50 | [ExcelColumn("列标题", Order = 1)] 51 | public string Name { get; set; } 52 | 53 | [ExcelColumn("日期", Format = "yyyy-MM-dd")] 54 | public DateTime? Date { get; set; } 55 | 56 | [ExcelColumn("金额", CellAlignment = HorizontalAlignment.Right)] 57 | public decimal Amount { get; set; } 58 | } 59 | ``` 60 | 61 | ## 开发指南 62 | 63 | ### 添加新功能 64 | 65 | 1. **保持兼容性**: 确保新功能在所有支持的框架版本上正常工作 66 | 2. **编写测试**: 为新功能添加 xUnit 测试用例 67 | 3. **更新文档**: 在 README.md 中添加功能说明和示例代码 68 | 4. **遵循 SemVer**: 根据语义化版本规范更新版本号 69 | 70 | ### 测试要求 71 | 72 | 1. **单元测试**: 使用 xUnit 编写测试 73 | 2. **测试命名**: 使用描述性的测试方法名,例如 `Should_ExportCorrectly_When_ModelHasNullValues` 74 | 3. **测试覆盖**: 确保核心功能和边界情况都有测试覆盖 75 | 4. **测试数据**: 测试用例应包含中文字符测试,确保 Unicode 支持 76 | 77 | ### 性能考虑 78 | 79 | 1. **大文件处理**: 考虑大型 Excel 文件的内存使用 80 | 2. **流式处理**: 对于大数据集,优先使用流式处理 81 | 3. **缓存机制**: 合理使用缓存避免重复计算 82 | 83 | ## 提交规范 84 | 85 | ### Commit Message 格式 86 | 87 | 建议使用约定式提交(Conventional Commits)格式: 88 | 89 | ``` 90 | <类型>[可选的作用域]: <描述> 91 | 92 | [可选的正文] 93 | 94 | [可选的脚注] 95 | ``` 96 | 97 | **类型**: 98 | - `feat`: 新功能 99 | - `fix`: 修复 bug 100 | - `docs`: 文档更新 101 | - `style`: 代码格式调整(不影响功能) 102 | - `refactor`: 重构代码 103 | - `test`: 添加或修改测试 104 | - `chore`: 构建过程或辅助工具的变动 105 | 106 | **示例**: 107 | ``` 108 | feat: 支持自定义日期格式导出 109 | fix: 修复空值导致的 NullReferenceException 110 | docs: 更新 README 中的安装说明 111 | ``` 112 | 113 | ### Pull Request 规范 114 | 115 | 1. **标题**: 简明扼要地描述变更内容 116 | 2. **描述**: 详细说明变更的原因、实现方式和影响 117 | 3. **关联 Issue**: 如果相关,使用 `Closes #123` 关联 Issue 118 | 4. **测试**: 说明如何测试变更 119 | 5. **Breaking Changes**: 明确标注破坏性变更 120 | 121 | ## 版本发布 122 | 123 | ### 版本号规范 124 | 125 | 项目使用语义化版本(Semantic Versioning): 126 | 127 | - **主版本号 (Major)**: 不兼容的 API 变更 128 | - **次版本号 (Minor)**: 向后兼容的功能新增 129 | - **修订号 (Patch)**: 向后兼容的问题修正 130 | 131 | 当前版本: `2.0.2` 132 | 133 | ### 发布流程 134 | 135 | 1. **更新版本号**: 在 `Chsword.Excel2Object.csproj` 中更新 `` 标签 136 | 2. **更新 README**: 在发布说明中添加版本更新内容 137 | 3. **创建 Git Tag**: 使用 `v{version}` 格式,例如 `v2.0.3` 138 | 4. **自动发布**: 推送 tag 后自动触发 NuGet 和 GitHub Release 发布 139 | 140 | ## 常见问题 141 | 142 | ### 多框架支持 143 | 144 | 项目支持多个目标框架,编码时注意: 145 | 146 | 1. 使用条件编译处理框架差异 147 | 2. 避免使用特定框架独有的 API 148 | 3. 针对不同框架的 API 差异提供兼容层 149 | 150 | ### NPOI 使用 151 | 152 | 1. NPOI 是项目的核心依赖,用于 Excel 文件操作 153 | 2. 注意 NPOI 的版本兼容性 154 | 3. 了解 NPOI 的内存管理,适当释放资源 155 | 156 | ### 中文支持 157 | 158 | 1. 确保所有功能正确处理中文字符 159 | 2. 字符宽度计算需考虑全角/半角字符差异 160 | 3. 日期格式支持中文格式(如"yyyy年MM月dd日") 161 | 162 | ## 贡献指南 163 | 164 | 我们欢迎各种形式的贡献: 165 | 166 | 1. **报告 Bug**: 提交详细的 Issue,包括重现步骤 167 | 2. **功能建议**: 在 Issue 或 Discussion 中讨论新功能 168 | 3. **代码贡献**: 提交 Pull Request,遵循上述规范 169 | 4. **文档改进**: 改进 README、示例代码和注释 170 | 171 | ## 资源链接 172 | 173 | - **项目主页**: https://github.com/chsword/Excel2Object 174 | - **NuGet 包**: https://www.nuget.org/packages/Chsword.Excel2Object 175 | - **NPOI 文档**: https://github.com/tonyqus/npoi 176 | - **问题讨论**: http://www.cnblogs.com/chsword/p/excel2object.html 177 | 178 | ## 许可证 179 | 180 | 本项目采用 MIT 许可证。详见 [LICENSE](../LICENSE) 文件。 181 | -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/ExcelTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.IO; 5 | using System.Linq; 6 | using Chsword.Excel2Object.Tests.Models; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using Newtonsoft.Json; 9 | 10 | namespace Chsword.Excel2Object.Tests; 11 | 12 | [TestClass] 13 | public class ExcelTest : BaseExcelTest 14 | { 15 | [TestMethod] 16 | public void ConvertXlsBytesTest() 17 | { 18 | var models = GetModels(); 19 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 20 | Assert.IsNotNull(bytes); 21 | 22 | Assert.IsTrue(bytes.Length > 0); 23 | 24 | var importer = new ExcelImporter(); 25 | var result = importer.ExcelToObject(bytes).ToList(); 26 | models.AreEqual(result); 27 | } 28 | 29 | 30 | [TestMethod] 31 | public void ConvertXlsFileTest() 32 | { 33 | var models = GetModels(); 34 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 35 | Assert.IsNotNull(bytes); 36 | var path = GetFilePath("test.xls"); 37 | File.WriteAllBytes(path, bytes); 38 | Assert.IsTrue(File.Exists(path)); 39 | var importer = new ExcelImporter(); 40 | var result = importer.ExcelToObject(path)!.ToList(); 41 | Assert.AreEqual(models.Count, result.Count); 42 | models.AreEqual(result); 43 | } 44 | 45 | [TestMethod] 46 | public void ConvertXlsFileUseObjectToExcelTest() 47 | { 48 | var models = GetModels(); 49 | var path = GetFilePath("test.xls"); 50 | ExcelHelper.ObjectToExcel(models, path); 51 | Assert.IsTrue(File.Exists(path)); 52 | var importer = new ExcelImporter(); 53 | var result = importer.ExcelToObject(path)!.ToList(); 54 | Assert.AreEqual(models.Count, result.Count); 55 | models.AreEqual(result); 56 | } 57 | 58 | [TestMethod] 59 | public void ConvertXlsFromDataTable() 60 | { 61 | var dt = new DataTable(); 62 | dt.Columns.Add(new DataColumn("姓名", typeof(string))); 63 | dt.Columns.Add(new DataColumn("Age", typeof(int))); 64 | var dr = dt.NewRow(); 65 | dr["姓名"] = "吴老狗"; 66 | dr["Age"] = 19; 67 | dt.Rows.Add(dr); 68 | var bytes = ExcelHelper.ObjectToExcelBytes(dt, ExcelType.Xls); 69 | Assert.IsNotNull(bytes); 70 | var path = GetFilePath("test.xls"); 71 | File.WriteAllBytes(path, bytes); 72 | Assert.IsTrue(File.Exists(path)); 73 | } 74 | 75 | [TestMethod] 76 | public void ConvertXlsxBytesTest() 77 | { 78 | var models = GetModels(); 79 | var array = ExcelHelper.ObjectToExcelBytes(models, ExcelType.Xlsx); 80 | Assert.IsNotNull(array); 81 | Assert.IsTrue(array.Length != 0); 82 | var excelImporter = new ExcelImporter(); 83 | var result = excelImporter.ExcelToObject(array).ToList(); 84 | models.AreEqual(result); 85 | } 86 | 87 | [TestMethod] 88 | public void ConvertXlsxFileTest() 89 | { 90 | var models = GetModels(); 91 | var bytes = ExcelHelper.ObjectToExcelBytes(models, ExcelType.Xlsx); 92 | Assert.IsNotNull(bytes); 93 | var path = GetFilePath("test.xlsx"); 94 | File.WriteAllBytes(path, bytes); 95 | Assert.IsTrue(File.Exists(path)); 96 | var importer = new ExcelImporter(); 97 | var result = importer.ExcelToObject(path)!.ToList(); 98 | Assert.AreEqual(models.Count, result.Count); 99 | models.AreEqual(result); 100 | } 101 | 102 | [TestMethod] 103 | public void ConvertXlsxWithDictionary() 104 | { 105 | var list = new List> 106 | { 107 | new() {["姓名"] = "吴老狗", ["Age"] = "19"}, 108 | new() {["姓名"] = "老林", ["Age"] = "50"} 109 | }; 110 | var bytes = ExcelHelper.ObjectToExcelBytes(list, ExcelType.Xlsx); 111 | Assert.IsNotNull(bytes); 112 | var path = GetFilePath("test.xlsx"); 113 | File.WriteAllBytes(path, bytes); 114 | var result = ExcelHelper.ExcelToObject>(bytes).ToList(); 115 | Assert.AreEqual( 116 | JsonConvert.SerializeObject(list), 117 | JsonConvert.SerializeObject(result) 118 | ); 119 | } 120 | 121 | private ReportModelCollection GetModels() 122 | { 123 | return new ReportModelCollection 124 | { 125 | new() 126 | { 127 | Name = "a", Title = "b", Enabled = true 128 | }, 129 | new() 130 | { 131 | Name = "c", Title = "d", Enabled = false 132 | }, 133 | new() 134 | { 135 | Name = "f", Title = "e", Uri = new Uri("http://chsword.cnblogs.com") 136 | } 137 | }; 138 | } 139 | } -------------------------------------------------------------------------------- /RELEASE_SCRIPT_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Release Script Usage Guide / 发布脚本使用指南 2 | 3 | ## English 4 | 5 | ### Prerequisites 6 | 7 | - **Windows**: PowerShell 5.1 or later (comes with Windows 10/11) 8 | - **Linux/macOS**: PowerShell Core 7.0+ ([Install PowerShell](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell)) 9 | 10 | ### Quick Start 11 | 12 | ```powershell 13 | # Auto-increment version and release 14 | .\release.ps1 15 | 16 | # Or release a specific version 17 | .\release.ps1 -Version 2.0.4 18 | 19 | # Get help 20 | .\release.ps1 -Help 21 | ``` 22 | 23 | The script will: 24 | 1. ✓ Validate version format 25 | 2. ✓ Update version in `.csproj` file 26 | 3. ✓ Commit changes 27 | 4. ✓ Create git tag 28 | 5. ✓ Push to remote repository 29 | 30 | ### Usage Examples 31 | 32 | #### Auto-Increment Release (New!) 33 | ```powershell 34 | .\release.ps1 35 | ``` 36 | Automatically increments the patch version (e.g., 2.0.4 → 2.0.5) and releases. 37 | 38 | #### Show Help (New!) 39 | ```powershell 40 | .\release.ps1 -Help 41 | ``` 42 | Displays usage information and examples. 43 | 44 | #### Basic Release 45 | ```powershell 46 | .\release.ps1 -Version 2.0.4 47 | ``` 48 | This will prompt for confirmation before each major step. 49 | 50 | #### Local-Only Release 51 | ```powershell 52 | .\release.ps1 -Version 2.0.4 -SkipPush 53 | ``` 54 | Creates commit and tag locally, but doesn't push to remote. 55 | 56 | #### Force Release (No Confirmations) 57 | ```powershell 58 | .\release.ps1 -Version 2.0.4 -Force 59 | ``` 60 | Skips all confirmation prompts. 61 | 62 | #### Combined Flags 63 | ```powershell 64 | .\release.ps1 -Version 2.0.4 -SkipPush -Force 65 | ``` 66 | Creates local commit/tag without pushing, no confirmations. 67 | 68 | ### Parameters 69 | 70 | | Parameter | Required | Description | 71 | |-----------|----------|-------------| 72 | | `-Version` | No | Version number in format: `Major.Minor.Patch` (e.g., `2.0.4`). If not specified, auto-increments the patch version | 73 | | `-SkipPush` | No | Create local commits and tags only, don't push to remote | 74 | | `-Force` | No | Skip all confirmation prompts | 75 | | `-Help` | No | Display help information | 76 | 77 | ### Troubleshooting 78 | 79 | #### Error: "Tag already exists" 80 | ```powershell 81 | # Delete local and remote tag 82 | git tag -d v2.0.4 83 | git push origin :refs/tags/v2.0.4 84 | 85 | # Then run the script again 86 | .\release.ps1 -Version 2.0.4 87 | ``` 88 | 89 | #### Error: "Execution policy" 90 | On Windows, you might need to allow script execution: 91 | ```powershell 92 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 93 | ``` 94 | 95 | #### Get Help 96 | ```powershell 97 | Get-Help .\release.ps1 -Detailed 98 | ``` 99 | 100 | --- 101 | 102 | ## 中文 103 | 104 | ### 前置要求 105 | 106 | - **Windows**: PowerShell 5.1 或更高版本(Windows 10/11 自带) 107 | - **Linux/macOS**: PowerShell Core 7.0+ ([安装 PowerShell](https://docs.microsoft.com/zh-cn/powershell/scripting/install/installing-powershell)) 108 | 109 | ### 快速开始 110 | 111 | ```powershell 112 | # 自动递增版本并发布 113 | .\release.ps1 114 | 115 | # 或发布指定版本 116 | .\release.ps1 -Version 2.0.4 117 | 118 | # 获取帮助 119 | .\release.ps1 -Help 120 | ``` 121 | 122 | 脚本将执行: 123 | 1. ✓ 验证版本号格式 124 | 2. ✓ 更新 `.csproj` 文件中的版本号 125 | 3. ✓ 提交更改 126 | 4. ✓ 创建 git 标签 127 | 5. ✓ 推送到远程仓库 128 | 129 | ### 使用示例 130 | 131 | #### 自动递增发布(新功能!) 132 | ```powershell 133 | .\release.ps1 134 | ``` 135 | 自动递增修订号(例如:2.0.4 → 2.0.5)并发布。 136 | 137 | #### 显示帮助(新功能!) 138 | ```powershell 139 | .\release.ps1 -Help 140 | ``` 141 | 显示使用说明和示例。 142 | 143 | #### 基本发布 144 | ```powershell 145 | .\release.ps1 -Version 2.0.4 146 | ``` 147 | 在每个主要步骤前会提示确认。 148 | 149 | #### 仅本地发布 150 | ```powershell 151 | .\release.ps1 -Version 2.0.4 -SkipPush 152 | ``` 153 | 在本地创建提交和标签,但不推送到远程。 154 | 155 | #### 强制发布(无确认) 156 | ```powershell 157 | .\release.ps1 -Version 2.0.4 -Force 158 | ``` 159 | 跳过所有确认提示。 160 | 161 | #### 组合参数 162 | ```powershell 163 | .\release.ps1 -Version 2.0.4 -SkipPush -Force 164 | ``` 165 | 创建本地提交/标签但不推送,无确认提示。 166 | 167 | ### 参数说明 168 | 169 | | 参数 | 必需 | 说明 | 170 | |------|------|------| 171 | | `-Version` | 否 | 版本号格式:`主版本.次版本.修订版`(例如:`2.0.4`)。如果不指定,则自动递增修订号 | 172 | | `-SkipPush` | 否 | 仅创建本地提交和标签,不推送到远程 | 173 | | `-Force` | 否 | 跳过所有确认提示 | 174 | | `-Help` | 否 | 显示帮助信息 | 175 | 176 | ### 故障排除 177 | 178 | #### 错误:"Tag 已存在" 179 | ```powershell 180 | # 删除本地和远程标签 181 | git tag -d v2.0.4 182 | git push origin :refs/tags/v2.0.4 183 | 184 | # 然后重新运行脚本 185 | .\release.ps1 -Version 2.0.4 186 | ``` 187 | 188 | #### 错误:"执行策略" 189 | 在 Windows 上,您可能需要允许脚本执行: 190 | ```powershell 191 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 192 | ``` 193 | 194 | #### 获取帮助 195 | ```powershell 196 | Get-Help .\release.ps1 -Detailed 197 | ``` 198 | 199 | --- 200 | 201 | ## See Also / 另见 202 | 203 | - [RELEASE_AUTOMATION.md](RELEASE_AUTOMATION.md) - Complete release automation documentation 204 | - [.github/VERSIONING.md](.github/VERSIONING.md) - Versioning guidelines 205 | - [.github/RELEASE_GUIDE.md](.github/RELEASE_GUIDE.md) - Detailed release process guide 206 | -------------------------------------------------------------------------------- /AutoColumnWidthDemo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Chsword.Excel2Object; 5 | using Chsword.Excel2Object.Tests.Models; 6 | 7 | namespace Chsword.Excel2Object.Demo; 8 | 9 | /// 10 | /// Demo program to showcase the new auto column width feature 11 | /// 12 | public class AutoColumnWidthDemo 13 | { 14 | public static void RunDemo() 15 | { 16 | Console.WriteLine("=== Excel2Object 自动列宽功能演示 ===\n"); 17 | 18 | // 准备测试数据 19 | var testData = new List 20 | { 21 | new() 22 | { 23 | Name = "张三", 24 | Age = 25, 25 | CreateTime = DateTime.Now 26 | }, 27 | new() 28 | { 29 | Name = "李四有一个很长的名字用来测试自动列宽功能", 30 | Age = 30, 31 | CreateTime = DateTime.Now.AddDays(-1) 32 | }, 33 | new() 34 | { 35 | Name = "王五", 36 | Age = 35, 37 | CreateTime = DateTime.Now.AddDays(-2) 38 | } 39 | }; 40 | 41 | // 测试1:启用自动列宽 42 | Console.WriteLine("1. 测试启用自动列宽功能:"); 43 | var bytesWithAutoWidth = ExcelHelper.ObjectToExcelBytes(testData, options => 44 | { 45 | options.ExcelType = ExcelType.Xlsx; 46 | options.AutoColumnWidth = true; 47 | options.MinColumnWidth = 8; // 最小宽度 48 | options.MaxColumnWidth = 40; // 最大宽度 49 | }); 50 | 51 | if (bytesWithAutoWidth != null) 52 | { 53 | File.WriteAllBytes("demo_auto_width.xlsx", bytesWithAutoWidth); 54 | Console.WriteLine("✅ 自动列宽Excel文件已生成: demo_auto_width.xlsx"); 55 | Console.WriteLine($" 文件大小: {bytesWithAutoWidth.Length} bytes"); 56 | } 57 | 58 | // 测试2:禁用自动列宽(使用固定宽度) 59 | Console.WriteLine("\n2. 测试固定列宽功能:"); 60 | var bytesWithFixedWidth = ExcelHelper.ObjectToExcelBytes(testData, options => 61 | { 62 | options.ExcelType = ExcelType.Xlsx; 63 | options.AutoColumnWidth = false; 64 | options.DefaultColumnWidth = 15; // 固定宽度 65 | }); 66 | 67 | if (bytesWithFixedWidth != null) 68 | { 69 | File.WriteAllBytes("demo_fixed_width.xlsx", bytesWithFixedWidth); 70 | Console.WriteLine("✅ 固定列宽Excel文件已生成: demo_fixed_width.xlsx"); 71 | Console.WriteLine($" 文件大小: {bytesWithFixedWidth.Length} bytes"); 72 | } 73 | 74 | // 测试3:测试中英文混合内容 75 | Console.WriteLine("\n3. 测试中英文混合内容的自动列宽:"); 76 | var mixedData = new List> 77 | { 78 | new() 79 | { 80 | ["Short"] = "A", 81 | ["Medium Length Text"] = "This is medium length content", 82 | ["很长的中文列名测试"] = "中文内容测试,包含宽字符" 83 | }, 84 | new() 85 | { 86 | ["Short"] = "B", 87 | ["Medium Length Text"] = "Short", 88 | ["很长的中文列名测试"] = "Mixed 中英文 content test" 89 | } 90 | }; 91 | 92 | var bytesWithMixed = ExcelHelper.ObjectToExcelBytes(mixedData, options => 93 | { 94 | options.ExcelType = ExcelType.Xlsx; 95 | options.AutoColumnWidth = true; 96 | options.MinColumnWidth = 5; 97 | options.MaxColumnWidth = 35; 98 | }); 99 | 100 | if (bytesWithMixed != null) 101 | { 102 | File.WriteAllBytes("demo_mixed_content.xlsx", bytesWithMixed); 103 | Console.WriteLine("✅ 中英文混合内容Excel文件已生成: demo_mixed_content.xlsx"); 104 | Console.WriteLine($" 文件大小: {bytesWithMixed.Length} bytes"); 105 | } 106 | 107 | // 验证导入功能 108 | Console.WriteLine("\n4. 验证导入功能:"); 109 | try 110 | { 111 | var importer = new ExcelImporter(); 112 | var importedData = importer.ExcelToObject(bytesWithAutoWidth!).ToList(); 113 | Console.WriteLine($"✅ 成功导入 {importedData.Count} 条记录"); 114 | 115 | foreach (var item in importedData) 116 | { 117 | Console.WriteLine($" - {item.Name}, 年龄: {item.Age}, 创建时间: {item.CreateTime:yyyy-MM-dd}"); 118 | } 119 | } 120 | catch (Exception ex) 121 | { 122 | Console.WriteLine($"❌ 导入失败: {ex.Message}"); 123 | } 124 | 125 | Console.WriteLine("\n=== 演示完成 ==="); 126 | Console.WriteLine("请检查生成的Excel文件,对比自动列宽和固定列宽的效果差异。"); 127 | } 128 | } 129 | 130 | // 程序入口(如果需要独立运行) 131 | public class Program 132 | { 133 | public static void Main(string[] args) 134 | { 135 | try 136 | { 137 | AutoColumnWidthDemo.RunDemo(); 138 | } 139 | catch (Exception ex) 140 | { 141 | Console.WriteLine($"程序执行出错: {ex.Message}"); 142 | Console.WriteLine($"详细信息: {ex}"); 143 | } 144 | 145 | Console.WriteLine("\n按任意键退出..."); 146 | Console.ReadKey(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Excel2Object 路线图 / Roadmap 2 | 3 | 本文档列出了 Excel2Object 项目未来可以开发的功能和改进。 4 | 5 | This document lists potential features and improvements for the Excel2Object project. 6 | 7 | [English](#english-version) | [中文](#中文版本) 8 | 9 | --- 10 | 11 | ## 中文版本 12 | 13 | ### 未来开发任务清单 14 | 15 | #### 1. 支持更多 Excel 日期时间格式 16 | **优先级:高** 17 | 18 | - 完善日期、日期时间、时间类型在 Excel 中的导入导出支持 19 | - 支持更多自定义日期时间格式 20 | - 处理不同地区的日期格式差异 21 | - 支持时区转换 22 | 23 | #### 2. 开发 CLI 命令行工具 24 | **优先级:中** 25 | 26 | - 提供独立的命令行工具用于 Excel 文件转换 27 | - 支持批量转换操作 28 | - 支持配置文件定义转换规则 29 | - 提供模板生成功能 30 | 31 | 示例命令: 32 | ```bash 33 | excel2obj convert input.xlsx --output data.json --type Model 34 | excel2obj generate-model input.xlsx --output Model.cs 35 | ``` 36 | 37 | #### 3. 增强公式支持 38 | **优先级:中** 39 | 40 | - 支持更复杂的 Excel 公式 41 | - 扩展内置函数库 42 | - 支持自定义函数 43 | - 支持公式的动态计算 44 | 45 | #### 4. 支持 Excel 图表导入导出 46 | **优先级:低** 47 | 48 | - 支持图表的读取和生成 49 | - 支持多种图表类型(柱状图、折线图、饼图等) 50 | - 支持图表数据的自动绑定 51 | - 支持图表样式自定义 52 | 53 | #### 5. 支持 Excel 数据验证 54 | **优先级:中** 55 | 56 | - 支持下拉列表数据验证 57 | - 支持数字范围验证 58 | - 支持日期范围验证 59 | - 支持自定义验证规则 60 | - 导出时保留验证规则 61 | 62 | #### 6. 性能优化与大文件支持 63 | **优先级:高** 64 | 65 | - 优化大数据量的导入导出性能 66 | - 支持流式读写大文件 67 | - 减少内存占用 68 | - 支持异步操作 69 | - 提供进度回调机制 70 | 71 | #### 7. 增强样式和格式支持 72 | **优先级:中** 73 | 74 | - 支持更多单元格样式(边框、背景、字体等) 75 | - 支持条件格式 76 | - 支持单元格合并 77 | - 支持行高列宽的更精细控制 78 | - 支持主题和模板 79 | 80 | #### 8. 支持复杂对象关系 81 | **优先级:中** 82 | 83 | - 支持嵌套对象的导入导出 84 | - 支持集合属性的展开 85 | - 支持主从表关系 86 | - 支持多对多关系的处理 87 | 88 | #### 9. 提供可视化配置工具 89 | **优先级:低** 90 | 91 | - 开发 Web 或桌面配置界面 92 | - 可视化设计 Excel 模板 93 | - 拖拽方式配置列映射 94 | - 实时预览转换结果 95 | - 保存和管理配置方案 96 | 97 | #### 10. 增强错误处理和日志 98 | **优先级:高** 99 | 100 | - 提供详细的错误信息和堆栈跟踪 101 | - 支持数据验证错误收集 102 | - 提供结构化日志输出 103 | - 支持自定义错误处理策略 104 | - 提供数据转换报告 105 | 106 | --- 107 | 108 | ## English Version 109 | 110 | ### Future Development Task List 111 | 112 | #### 1. Support More Excel Date/Time Formats 113 | **Priority: High** 114 | 115 | - Enhance import/export support for date, datetime, and time types in Excel 116 | - Support more custom date/time formats 117 | - Handle regional date format differences 118 | - Support timezone conversion 119 | 120 | #### 2. Develop CLI Command-Line Tool 121 | **Priority: Medium** 122 | 123 | - Provide standalone command-line tool for Excel file conversion 124 | - Support batch conversion operations 125 | - Support configuration files to define conversion rules 126 | - Provide template generation functionality 127 | 128 | Example commands: 129 | ```bash 130 | excel2obj convert input.xlsx --output data.json --type Model 131 | excel2obj generate-model input.xlsx --output Model.cs 132 | ``` 133 | 134 | #### 3. Enhanced Formula Support 135 | **Priority: Medium** 136 | 137 | - Support more complex Excel formulas 138 | - Extend built-in function library 139 | - Support custom functions 140 | - Support dynamic formula calculation 141 | 142 | #### 4. Support Excel Chart Import/Export 143 | **Priority: Low** 144 | 145 | - Support reading and generating charts 146 | - Support multiple chart types (column, line, pie, etc.) 147 | - Support automatic chart data binding 148 | - Support chart style customization 149 | 150 | #### 5. Support Excel Data Validation 151 | **Priority: Medium** 152 | 153 | - Support dropdown list validation 154 | - Support number range validation 155 | - Support date range validation 156 | - Support custom validation rules 157 | - Preserve validation rules on export 158 | 159 | #### 6. Performance Optimization and Large File Support 160 | **Priority: High** 161 | 162 | - Optimize import/export performance for large datasets 163 | - Support streaming read/write for large files 164 | - Reduce memory footprint 165 | - Support asynchronous operations 166 | - Provide progress callback mechanism 167 | 168 | #### 7. Enhanced Style and Format Support 169 | **Priority: Medium** 170 | 171 | - Support more cell styles (borders, backgrounds, fonts, etc.) 172 | - Support conditional formatting 173 | - Support cell merging 174 | - Support finer control over row heights and column widths 175 | - Support themes and templates 176 | 177 | #### 8. Support Complex Object Relationships 178 | **Priority: Medium** 179 | 180 | - Support import/export of nested objects 181 | - Support expansion of collection properties 182 | - Support master-detail table relationships 183 | - Handle many-to-many relationships 184 | 185 | #### 9. Provide Visual Configuration Tool 186 | **Priority: Low** 187 | 188 | - Develop Web or desktop configuration interface 189 | - Visually design Excel templates 190 | - Drag-and-drop configuration for column mapping 191 | - Real-time preview of conversion results 192 | - Save and manage configuration schemes 193 | 194 | #### 10. Enhanced Error Handling and Logging 195 | **Priority: High** 196 | 197 | - Provide detailed error messages and stack traces 198 | - Support data validation error collection 199 | - Provide structured log output 200 | - Support custom error handling strategies 201 | - Provide data conversion reports 202 | 203 | --- 204 | 205 | ## 贡献指南 / Contributing 206 | 207 | 如果您想参与上述任务的开发,或有其他建议,欢迎: 208 | 209 | If you want to participate in the development of the above tasks or have other suggestions, welcome to: 210 | 211 | - 提交 Issue 讨论功能需求 / Submit an Issue to discuss feature requests 212 | - 提交 Pull Request 贡献代码 / Submit a Pull Request to contribute code 213 | - 在 Discussions 中分享想法 / Share ideas in Discussions 214 | 215 | 感谢您对 Excel2Object 的关注和支持! 216 | 217 | Thank you for your attention and support for Excel2Object! 218 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | *.local.cs -------------------------------------------------------------------------------- /.github/RELEASE_CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # 发布检查清单 / Release Checklist 2 | 3 | 使用此清单确保发布流程的每个步骤都已完成。 4 | 5 | Use this checklist to ensure every step of the release process is completed. 6 | 7 | --- 8 | 9 | ## 📋 发布前检查 / Pre-Release Checklist 10 | 11 | ### 代码质量 / Code Quality 12 | - [ ] 所有功能已完成开发 13 | - [ ] 代码已通过 Code Review 14 | - [ ] 所有单元测试通过(本地运行 `dotnet test`) 15 | - [ ] 没有编译警告或错误 16 | - [ ] 代码符合项目编码规范 17 | 18 | ### 版本管理 / Version Management 19 | - [ ] 确定新版本号(根据 [VERSIONING.md](.github/VERSIONING.md) 规范) 20 | - [ ] Bug 修复 → Patch +1 21 | - [ ] 新功能 → Minor +1 22 | - [ ] 破坏性变更 → Major +1 23 | - [ ] 更新 `Chsword.Excel2Object/Chsword.Excel2Object.csproj` 中的 `` 标签 24 | - [ ] 版本号格式正确(X.Y.Z 或 X.Y.Z-prerelease) 25 | 26 | ### 文档更新 / Documentation Update 27 | - [ ] 在 `README.md` 中添加中文发布说明 28 | - [ ] 日期格式正确(YYYY.MM.DD) 29 | - [ ] 版本号正确(vX.Y.Z) 30 | - [ ] 变更说明清晰完整 31 | - [ ] 使用适当的 emoji 和格式 32 | - [ ] 在 `README_EN.md` 中添加英文发布说明 33 | - [ ] 与中文版本保持同步 34 | - [ ] 翻译准确 35 | - [ ] 如有新功能,更新示例代码 36 | - [ ] 如有 API 变更,更新 API 文档 37 | 38 | ### 功能验证 / Feature Verification 39 | - [ ] 新功能已手动测试 40 | - [ ] 示例代码可以正常运行 41 | - [ ] 在多个框架版本上测试(如可能) 42 | - [ ] .NET Framework 4.7.2 43 | - [ ] .NET Standard 2.0 44 | - [ ] .NET 6.0 45 | - [ ] .NET 8.0 46 | - [ ] 向后兼容性验证(对于非 Major 版本) 47 | 48 | ### 破坏性变更检查 / Breaking Changes Check 49 | - [ ] 识别所有破坏性变更 50 | - [ ] 在发布说明中明确标注 💥 Breaking Changes 51 | - [ ] 提供迁移指南(如需要) 52 | - [ ] 更新示例代码以适应新 API 53 | 54 | ### NuGet 包验证 / NuGet Package Verification 55 | - [ ] 本地构建 NuGet 包成功 56 | ```bash 57 | dotnet pack --configuration Release --output ./artifacts 58 | ``` 59 | - [ ] 检查包内容是否正确 60 | ```bash 61 | # 解压 .nupkg 文件检查内容 62 | ``` 63 | - [ ] 包大小合理(不包含不必要的文件) 64 | - [ ] 所有目标框架的 DLL 都包含在包中 65 | 66 | --- 67 | 68 | ## 🚀 发布执行 / Release Execution 69 | 70 | ### Git 操作 / Git Operations 71 | - [ ] 所有变更已提交到 main 分支 72 | ```bash 73 | git add . 74 | git commit -m "chore: bump version to X.Y.Z" 75 | git push origin main 76 | ``` 77 | - [ ] 创建 Git Tag(格式:vX.Y.Z) 78 | ```bash 79 | git tag vX.Y.Z 80 | # 或带注释的 tag 81 | git tag -a vX.Y.Z -m "Release version X.Y.Z" 82 | ``` 83 | - [ ] 推送 Tag 到远程仓库 84 | ```bash 85 | git push origin vX.Y.Z 86 | ``` 87 | 88 | ### 自动化监控 / Automation Monitoring 89 | - [ ] 访问 GitHub Actions 页面监控流程 90 | - URL: https://github.com/chsword/Excel2Object/actions 91 | - [ ] 确认 `validate-version` 作业通过 92 | - [ ] 确认 `build-and-test` 作业通过(所有平台) 93 | - [ ] 确认 `pack-nuget` 作业通过 94 | - [ ] 确认 `publish-nuget` 作业通过 95 | - [ ] 确认 `create-release` 作业通过 96 | 97 | ### 故障处理 / Failure Handling 98 | 如果任何作业失败: 99 | - [ ] 查看失败作业的日志 100 | - [ ] 识别失败原因 101 | - [ ] 修复问题 102 | - [ ] 删除失败的 Tag 103 | ```bash 104 | git tag -d vX.Y.Z 105 | git push origin :refs/tags/vX.Y.Z 106 | ``` 107 | - [ ] 递增版本号(如需要) 108 | - [ ] 重新执行发布流程 109 | 110 | --- 111 | 112 | ## ✅ 发布后验证 / Post-Release Verification 113 | 114 | ### NuGet.org 验证 / NuGet.org Verification 115 | - [ ] 访问 NuGet.org 搜索包 116 | - URL: https://www.nuget.org/packages/Chsword.Excel2Object/ 117 | - [ ] 确认新版本已列出 118 | - [ ] 检查包版本号正确 119 | - [ ] 检查包元数据(作者、描述、标签等) 120 | - [ ] 确认包可以被搜索到(可能需要几分钟) 121 | - [ ] 下载统计开始计数 122 | 123 | ### GitHub Release 验证 / GitHub Release Verification 124 | - [ ] 访问 GitHub Releases 页面 125 | - URL: https://github.com/chsword/Excel2Object/releases 126 | - [ ] 确认新 Release 已创建 127 | - [ ] 检查 Release 标题和标签正确 128 | - [ ] 验证发布说明内容完整 129 | - [ ] 确认 NuGet 包文件已附加 130 | - [ ] 预发布标记正确(如适用) 131 | 132 | ### 功能测试 / Functional Testing 133 | - [ ] 在新项目中安装 NuGet 包 134 | ```bash 135 | dotnet new console -n TestProject 136 | cd TestProject 137 | dotnet add package Chsword.Excel2Object --version X.Y.Z 138 | ``` 139 | - [ ] 运行示例代码验证功能 140 | - [ ] 确认没有运行时错误 141 | - [ ] 验证新功能工作正常 142 | 143 | ### 文档验证 / Documentation Verification 144 | - [ ] README 徽章显示正确的版本号 145 | - [ ] 文档链接都可访问 146 | - [ ] 示例代码与新版本匹配 147 | - [ ] API 文档(如有)已更新 148 | 149 | --- 150 | 151 | ## 📢 发布通知 / Release Announcement 152 | 153 | ### 内部通知 / Internal Notification 154 | - [ ] 通知团队成员发布完成 155 | - [ ] 在项目 Discussion 中发布公告 156 | - [ ] 更新项目看板或 Milestone 157 | 158 | ### 社区通知 / Community Notification 159 | - [ ] 在项目主页添加发布公告(如适用) 160 | - [ ] 在博客或社交媒体分享(可选) 161 | - [ ] 回复相关 Issue 通知问题已修复(如适用) 162 | 163 | --- 164 | 165 | ## 📊 发布后跟踪 / Post-Release Tracking 166 | 167 | ### 第一天 / First Day 168 | - [ ] 监控 NuGet 下载量 169 | - [ ] 检查是否有新的 Issue 报告 170 | - [ ] 回应社区反馈 171 | 172 | ### 第一周 / First Week 173 | - [ ] 收集用户反馈 174 | - [ ] 记录任何问题或改进建议 175 | - [ ] 评估是否需要发布 Patch 版本 176 | 177 | --- 178 | 179 | ## 🔄 回滚准备 / Rollback Preparation 180 | 181 | 如果发现严重问题需要回滚: 182 | 183 | ### NuGet 回滚 / NuGet Rollback 184 | - [ ] 登录 NuGet.org 185 | - [ ] 找到问题版本 186 | - [ ] 点击 "Unlist" 从搜索中移除 187 | - [ ] 注意:已安装的用户仍可使用 188 | 189 | ### GitHub Release 回滚 / GitHub Release Rollback 190 | - [ ] 删除或编辑 Release 页面 191 | - [ ] 添加警告说明 192 | - [ ] 可选:删除 Git Tag 193 | 194 | ### 修复版本 / Fix Version 195 | - [ ] 修复问题 196 | - [ ] 递增版本号(通常是 Patch +1) 197 | - [ ] 重新执行发布流程 198 | 199 | --- 200 | 201 | ## 📝 经验总结 / Lessons Learned 202 | 203 | 发布完成后,记录经验教训: 204 | 205 | - [ ] 本次发布遇到的问题 206 | - [ ] 解决方案和改进建议 207 | - [ ] 更新文档和流程(如需要) 208 | - [ ] 分享给团队成员 209 | 210 | --- 211 | 212 | ## ✨ 发布完成 / Release Complete 213 | 214 | 🎉 恭喜!发布流程已完成! 215 | 216 | 🎉 Congratulations! The release process is complete! 217 | 218 | **发布信息 / Release Information:** 219 | - 版本号 / Version: ____________ 220 | - 发布日期 / Release Date: ____________ 221 | - NuGet 链接 / NuGet Link: https://www.nuget.org/packages/Chsword.Excel2Object/____________ 222 | - Release 链接 / Release Link: https://github.com/chsword/Excel2Object/releases/tag/v____________ 223 | 224 | **执行人 / Released By:** ____________ 225 | 226 | **特别说明 / Special Notes:** 227 | ____________________________________________ 228 | ____________________________________________ 229 | ____________________________________________ 230 | -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/AutoColumnWidthTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Chsword.Excel2Object.Tests.Models; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using Newtonsoft.Json; 7 | 8 | namespace Chsword.Excel2Object.Tests; 9 | 10 | [TestClass] 11 | public class AutoColumnWidthTest : BaseExcelTest 12 | { 13 | [TestMethod] 14 | public void TestAutoColumnWidthEnabled() 15 | { 16 | var models = GetTestModels(); 17 | 18 | // Test with auto column width enabled 19 | var bytes = ExcelHelper.ObjectToExcelBytes(models, options => 20 | { 21 | options.ExcelType = ExcelType.Xlsx; 22 | options.AutoColumnWidth = true; 23 | options.MinColumnWidth = 10; 24 | options.MaxColumnWidth = 30; 25 | }); 26 | 27 | Assert.IsNotNull(bytes); 28 | Assert.IsTrue(bytes.Length > 0); 29 | 30 | // Verify the export can be imported back 31 | var importer = new ExcelImporter(); 32 | var result = importer.ExcelToObject(bytes).ToList(); 33 | Assert.AreEqual(3, result.Count); 34 | Assert.AreEqual("张三", result[0].Name); 35 | } 36 | 37 | [TestMethod] 38 | public void TestAutoColumnWidthDisabled() 39 | { 40 | var models = GetTestModels(); 41 | 42 | // Test with auto column width disabled (default behavior) 43 | var bytes = ExcelHelper.ObjectToExcelBytes(models, options => 44 | { 45 | options.ExcelType = ExcelType.Xlsx; 46 | options.AutoColumnWidth = false; 47 | options.DefaultColumnWidth = 20; 48 | }); 49 | 50 | Assert.IsNotNull(bytes); 51 | Assert.IsTrue(bytes.Length > 0); 52 | 53 | // Verify the export can be imported back 54 | var importer = new ExcelImporter(); 55 | var result = importer.ExcelToObject(bytes).ToList(); 56 | Assert.AreEqual(3, result.Count); 57 | } 58 | 59 | [TestMethod] 60 | public void TestAutoColumnWidthWithLongContent() 61 | { 62 | var models = GetLongContentModels(); 63 | 64 | // Test with very long content 65 | var bytes = ExcelHelper.ObjectToExcelBytes(models, options => 66 | { 67 | options.ExcelType = ExcelType.Xlsx; 68 | options.AutoColumnWidth = true; 69 | options.MinColumnWidth = 5; 70 | options.MaxColumnWidth = 50; // Should be capped at this value 71 | }); 72 | 73 | Assert.IsNotNull(bytes); 74 | Assert.IsTrue(bytes.Length > 0); 75 | 76 | // Verify the export works 77 | var importer = new ExcelImporter(); 78 | var result = importer.ExcelToObject(bytes).ToList(); 79 | Assert.AreEqual(2, result.Count); 80 | } 81 | 82 | [TestMethod] 83 | public void TestAutoColumnWidthWithMixedContent() 84 | { 85 | var models = new List> 86 | { 87 | new() 88 | { 89 | ["短名"] = "A", 90 | ["Medium Length Name"] = "This is a medium length content", 91 | ["很长的中文列名用于测试宽字符"] = "中文内容测试,包含宽字符" 92 | }, 93 | new() 94 | { 95 | ["短名"] = "B", 96 | ["Medium Length Name"] = "Short", 97 | ["很长的中文列名用于测试宽字符"] = "English mixed 中文 content" 98 | } 99 | }; 100 | 101 | var bytes = ExcelHelper.ObjectToExcelBytes(models, options => 102 | { 103 | options.ExcelType = ExcelType.Xlsx; 104 | options.AutoColumnWidth = true; 105 | options.MinColumnWidth = 8; 106 | options.MaxColumnWidth = 40; 107 | }); 108 | 109 | Assert.IsNotNull(bytes); 110 | Assert.IsTrue(bytes.Length > 0); 111 | 112 | // Verify the export can be imported back as dictionary 113 | var importer = new ExcelImporter(); 114 | var result = importer.ExcelToObject>(bytes).ToList(); 115 | Assert.AreEqual(2, result.Count); 116 | Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented)); 117 | } 118 | 119 | private IEnumerable GetTestModels() 120 | { 121 | return new List 122 | { 123 | new() 124 | { 125 | Name = "张三", 126 | Age = 25, 127 | CreateTime = DateTime.Now 128 | }, 129 | new() 130 | { 131 | Name = "李四", 132 | Age = 30, 133 | CreateTime = DateTime.Now.AddDays(-1) 134 | }, 135 | new() 136 | { 137 | Name = "王五", 138 | Age = 35, 139 | CreateTime = DateTime.Now.AddDays(-2) 140 | } 141 | }; 142 | } 143 | 144 | private IEnumerable GetLongContentModels() 145 | { 146 | return new List 147 | { 148 | new() 149 | { 150 | Name = "This is a very long name that should test the maximum column width constraint functionality", 151 | Age = 25, 152 | CreateTime = DateTime.Now 153 | }, 154 | new() 155 | { 156 | Name = "Another extremely long name with lots of characters to verify that the auto width calculation works properly with maximum limits", 157 | Age = 30, 158 | CreateTime = DateTime.Now.AddDays(-1) 159 | } 160 | }; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /.github/VERSIONING.md: -------------------------------------------------------------------------------- 1 | # 版本管理规范 / Versioning Guidelines 2 | 3 | [中文](#中文版本) | [English](#english-version) 4 | 5 | --- 6 | 7 | ## 中文版本 8 | 9 | ### 版本号格式 10 | 11 | Excel2Object 项目遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范。 12 | 13 | 版本号格式:`主版本号.次版本号.修订号` (例如: `2.0.2`) 14 | 15 | ### 版本号递增规则 16 | 17 | #### 1. 主版本号 (Major Version) - X.0.0 18 | 19 | **何时递增**:当你做了不兼容的 API 修改 20 | 21 | **示例场景**: 22 | - 删除或重命名公共 API 23 | - 更改方法签名导致破坏性变更 24 | - 移除对旧框架版本的支持 25 | - 重大架构调整 26 | 27 | **递增方式**: 28 | - 主版本号 +1 29 | - 次版本号和修订号重置为 0 30 | - 例如:`2.0.2` → `3.0.0` 31 | 32 | #### 2. 次版本号 (Minor Version) - 0.X.0 33 | 34 | **何时递增**:当你做了向下兼容的功能性新增 35 | 36 | **示例场景**: 37 | - 添加新的公共方法或属性 38 | - 新增对新 Excel 格式的支持 39 | - 添加新的特性或功能增强 40 | - 添加对新框架版本的支持 41 | 42 | **递增方式**: 43 | - 次版本号 +1 44 | - 修订号重置为 0 45 | - 例如:`2.0.2` → `2.1.0` 46 | 47 | #### 3. 修订号 (Patch Version) - 0.0.X 48 | 49 | **何时递增**:当你做了向下兼容的问题修正 50 | 51 | **示例场景**: 52 | - 修复 Bug 53 | - 性能优化(不改变 API) 54 | - 依赖项版本更新(安全更新) 55 | - 文档修正 56 | 57 | **递增方式**: 58 | - 修订号 +1 59 | - 例如:`2.0.2` → `2.0.3` 60 | 61 | ### 版本号管理流程 62 | 63 | #### 开发阶段 64 | 65 | 1. 在功能分支上开发新功能或修复 bug 66 | 2. 确保所有测试通过 67 | 3. 在 PR 中说明变更类型(Major/Minor/Patch) 68 | 69 | #### 发布前准备 70 | 71 | 1. **确定版本号**:根据变更内容确定新版本号 72 | 2. **更新 csproj 文件**: 73 | ```xml 74 | 2.0.3 75 | ``` 76 | 3. **更新 README.md**:在发布说明部分添加新版本信息 77 | ```markdown 78 | * **YYYY.MM.DD** - vX.Y.Z 79 | - [x] 变更说明1 80 | - [x] 变更说明2 81 | ``` 82 | 4. **更新 README_EN.md**:同步更新英文文档 83 | 84 | #### 发布流程 85 | 86 | 1. **合并到主分支**:将包含版本更新的 PR 合并到 `main` 分支 87 | 2. **创建 Git Tag**: 88 | ```bash 89 | git tag v2.0.3 90 | git push origin v2.0.3 91 | ``` 92 | 3. **自动发布**: 93 | - 推送 tag 后,GitHub Actions 自动触发 94 | - 自动构建并发布 NuGet 包 95 | - 自动创建 GitHub Release 96 | 97 | ### 版本历史参考 98 | 99 | 项目版本演进历史: 100 | 101 | - **v2.0.2** (2025.10.11) - 全面的日期/时间格式支持 102 | - **v2.0.1** (2025.07.23) - 基于内容的自动列宽调整 103 | - **v2.0.0** - 重大架构更新 104 | - **v1.x.x** - 早期版本 105 | 106 | ### 预发布版本 107 | 108 | 如需发布预发布版本(Alpha、Beta、RC),使用以下格式: 109 | 110 | - Alpha: `2.1.0-alpha.1` 111 | - Beta: `2.1.0-beta.1` 112 | - RC: `2.1.0-rc.1` 113 | 114 | 预发布版本不会自动发布到 NuGet.org,需要手动发布。 115 | 116 | ### 自动化版本检查 117 | 118 | 项目使用 GitHub Actions 进行自动化版本检查: 119 | 120 | 1. **版本格式验证**:确保版本号符合 SemVer 格式 121 | 2. **版本号一致性**:检查 csproj 中的版本号与 tag 版本号一致 122 | 3. **版本号递增**:确保新版本号大于当前最新版本 123 | 124 | --- 125 | 126 | ## English Version 127 | 128 | ### Version Number Format 129 | 130 | The Excel2Object project follows [Semantic Versioning 2.0.0](https://semver.org/). 131 | 132 | Version format: `MAJOR.MINOR.PATCH` (e.g., `2.0.2`) 133 | 134 | ### Version Increment Rules 135 | 136 | #### 1. Major Version - X.0.0 137 | 138 | **When to increment**: When you make incompatible API changes 139 | 140 | **Example scenarios**: 141 | - Removing or renaming public APIs 142 | - Changing method signatures that break compatibility 143 | - Dropping support for old framework versions 144 | - Major architecture changes 145 | 146 | **How to increment**: 147 | - Major +1 148 | - Minor and Patch reset to 0 149 | - Example: `2.0.2` → `3.0.0` 150 | 151 | #### 2. Minor Version - 0.X.0 152 | 153 | **When to increment**: When you add functionality in a backward compatible manner 154 | 155 | **Example scenarios**: 156 | - Adding new public methods or properties 157 | - Adding support for new Excel formats 158 | - Adding new features or enhancements 159 | - Adding support for new framework versions 160 | 161 | **How to increment**: 162 | - Minor +1 163 | - Patch reset to 0 164 | - Example: `2.0.2` → `2.1.0` 165 | 166 | #### 3. Patch Version - 0.0.X 167 | 168 | **When to increment**: When you make backward compatible bug fixes 169 | 170 | **Example scenarios**: 171 | - Bug fixes 172 | - Performance improvements (without API changes) 173 | - Dependency updates (security patches) 174 | - Documentation corrections 175 | 176 | **How to increment**: 177 | - Patch +1 178 | - Example: `2.0.2` → `2.0.3` 179 | 180 | ### Version Management Workflow 181 | 182 | #### Development Phase 183 | 184 | 1. Develop features or fixes in feature branches 185 | 2. Ensure all tests pass 186 | 3. Specify change type (Major/Minor/Patch) in PR 187 | 188 | #### Pre-release Preparation 189 | 190 | 1. **Determine version number**: Based on change type 191 | 2. **Update csproj file**: 192 | ```xml 193 | 2.0.3 194 | ``` 195 | 3. **Update README.md**: Add new version to release notes 196 | ```markdown 197 | * **YYYY.MM.DD** - vX.Y.Z 198 | - [x] Change description 1 199 | - [x] Change description 2 200 | ``` 201 | 4. **Update README_EN.md**: Sync English documentation 202 | 203 | #### Release Process 204 | 205 | 1. **Merge to main**: Merge version update PR to `main` branch 206 | 2. **Create Git Tag**: 207 | ```bash 208 | git tag v2.0.3 209 | git push origin v2.0.3 210 | ``` 211 | 3. **Automatic Release**: 212 | - GitHub Actions triggers automatically on tag push 213 | - Automatically builds and publishes NuGet package 214 | - Automatically creates GitHub Release 215 | 216 | ### Version History Reference 217 | 218 | Project version evolution: 219 | 220 | - **v2.0.2** (2025.10.11) - Comprehensive date/time format support 221 | - **v2.0.1** (2025.07.23) - Auto column width based on content 222 | - **v2.0.0** - Major architecture update 223 | - **v1.x.x** - Early versions 224 | 225 | ### Pre-release Versions 226 | 227 | For pre-release versions (Alpha, Beta, RC), use the following format: 228 | 229 | - Alpha: `2.1.0-alpha.1` 230 | - Beta: `2.1.0-beta.1` 231 | - RC: `2.1.0-rc.1` 232 | 233 | Pre-release versions are not automatically published to NuGet.org and require manual publishing. 234 | 235 | ### Automated Version Checks 236 | 237 | The project uses GitHub Actions for automated version checks: 238 | 239 | 1. **Version format validation**: Ensures version follows SemVer format 240 | 2. **Version consistency**: Checks csproj version matches tag version 241 | 3. **Version increment**: Ensures new version is greater than latest version 242 | -------------------------------------------------------------------------------- /Chsword.Excel2Object/Internal/TypeConvert.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Linq.Expressions; 3 | using Chsword.Excel2Object.Options; 4 | 5 | namespace Chsword.Excel2Object.Internal; 6 | 7 | internal static class TypeConvert 8 | { 9 | public static ExcelModel ConvertDictionaryToExcelModel(IEnumerable> data, 10 | ExcelExporterOptions options) 11 | { 12 | var sheetTitle = options.SheetTitle; 13 | var excel = new ExcelModel {Sheets = new List()}; 14 | var sheet = SheetModel.Create(sheetTitle); 15 | excel.Sheets.Add(sheet); 16 | var list = data.ToList(); 17 | var title = list.FirstOrDefault(); 18 | if (title == null) return excel; 19 | var columns = title.Keys.Select((c, i) => new ExcelColumn 20 | { 21 | Order = i, 22 | Title = c, 23 | Type = typeof(string) 24 | }).ToList(); 25 | 26 | sheet.Columns = AttachColumns(columns, options); 27 | sheet.Rows = list; 28 | 29 | return excel; 30 | } 31 | 32 | public static ExcelModel ConvertObjectToExcelModel(IEnumerable data, 33 | ExcelExporterOptions options) 34 | { 35 | var sheetTitle = options.SheetTitle; 36 | var excel = new ExcelModel {Sheets = new List()}; 37 | if (string.IsNullOrWhiteSpace(sheetTitle)) 38 | { 39 | var classAttr = ExcelUtil.GetClassExportAttribute(); 40 | sheetTitle = classAttr == null ? sheetTitle : classAttr.Title; 41 | } 42 | 43 | var sheet = SheetModel.Create(sheetTitle); 44 | 45 | excel.Sheets.Add(sheet); 46 | var attrDict = ExcelUtil.GetPropertiesAttributesDict(); 47 | var objKeysArray = attrDict.OrderBy(c => c.Value.Order).ToArray(); 48 | 49 | var columns = new List(); 50 | for (var i = 0; i < objKeysArray.Length; i++) 51 | { 52 | var titleAttr = objKeysArray[i].Value; 53 | var column = new ExcelColumn 54 | { 55 | Title = titleAttr.Title, 56 | Type = objKeysArray[i].Key.PropertyType, 57 | Order = i 58 | }; 59 | if (titleAttr is ExcelColumnAttribute excelColumnAttr) 60 | { 61 | column.CellStyle = excelColumnAttr; 62 | column.HeaderStyle = excelColumnAttr; 63 | } 64 | 65 | columns.Add(column); 66 | } 67 | 68 | sheet.Columns = AttachColumns(columns, options); 69 | foreach (var item in data.Where(c => c != null)) 70 | { 71 | var row = new Dictionary(); 72 | foreach (var column in objKeysArray) 73 | { 74 | var prop = column.Key; 75 | row[column.Value.Title] = prop.GetValue(item, null); 76 | } 77 | 78 | sheet.Rows.Add(row); 79 | } 80 | 81 | return excel; 82 | } 83 | 84 | internal static ExcelModel ConvertDataSetToExcelModel(DataTable dt, ExcelExporterOptions options) 85 | { 86 | var sheetTitle = options.SheetTitle; 87 | var excel = new ExcelModel {Sheets = new List()}; 88 | var sheet = SheetModel.Create(sheetTitle); 89 | excel.Sheets.Add(sheet); 90 | var dataSetColumnArray = dt.Columns.Cast().ToArray(); 91 | 92 | var columns = dataSetColumnArray.Select((item, i) => 93 | new ExcelColumn 94 | { 95 | Order = i, 96 | Title = item.ColumnName, 97 | Type = item.DataType 98 | }).ToList(); 99 | sheet.Columns = AttachColumns(columns, options); 100 | 101 | var data = dt.Rows.Cast().ToArray(); 102 | foreach (var item in data.Where(c => c != null)) 103 | { 104 | var row = new Dictionary(); 105 | foreach (var column in dataSetColumnArray) row[column.ColumnName] = item[column.ColumnName]; 106 | 107 | sheet.Rows.Add(row); 108 | } 109 | 110 | return excel; 111 | } 112 | 113 | private static List AttachColumns(List columns, ExcelExporterOptions options) 114 | { 115 | columns = columns.OrderBy(c => c.Order).ToList(); 116 | foreach (var formulaColumn in options.FormulaColumns) 117 | { 118 | var excelColumn = columns.FirstOrDefault(c => c.Title == formulaColumn.Title); 119 | if (excelColumn == null) 120 | { 121 | excelColumn = new ExcelColumn 122 | { 123 | Title = formulaColumn.Title, 124 | Order = 0, 125 | Type = typeof(Expression), 126 | Formula = formulaColumn.Formula, 127 | ResultType = formulaColumn.FormulaResultType 128 | }; 129 | if (string.IsNullOrWhiteSpace(formulaColumn.AfterColumnTitle)) 130 | { 131 | columns.Add(excelColumn); 132 | } 133 | else 134 | { 135 | var i = columns.FindIndex(c => c.Title == formulaColumn.AfterColumnTitle); 136 | if (i < 0) 137 | throw new Excel2ObjectException( 138 | $"can not find {formulaColumn.AfterColumnTitle} column."); 139 | 140 | columns.Insert(i + 1, excelColumn); 141 | } 142 | } 143 | else 144 | { 145 | excelColumn.Type = typeof(Expression); 146 | excelColumn.Formula = formulaColumn.Formula; 147 | excelColumn.ResultType = formulaColumn.FormulaResultType; 148 | } 149 | } 150 | 151 | for (var i = 0; i < columns.Count; i++) columns[i].Order = i * 10; 152 | 153 | return columns; 154 | } 155 | } -------------------------------------------------------------------------------- /Chsword.Excel2Object/Internal/ExpressionConvert.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Chsword.Excel2Object.Functions; 3 | 4 | namespace Chsword.Excel2Object.Internal; 5 | 6 | internal class ExpressionConvert 7 | { 8 | private static readonly Type[] CallMethodTypes = 9 | { 10 | typeof(IMathFunction), 11 | typeof(IStatisticsFunction), 12 | typeof(IConditionFunction), 13 | typeof(IReferenceFunction), 14 | typeof(IDateTimeFunction), 15 | typeof(ITextFunction), 16 | typeof(IAllFunction) 17 | }; 18 | 19 | public ExpressionConvert(string[] columns, int rowIndex) 20 | { 21 | Columns = columns; 22 | RowIndex = rowIndex; 23 | } 24 | 25 | private static Dictionary BinarySymbolDictionary { get; } = 26 | new() 27 | { 28 | [ExpressionType.Add] = "+", 29 | [ExpressionType.Subtract] = "-", 30 | [ExpressionType.Multiply] = "*", 31 | [ExpressionType.Divide] = "/", 32 | [ExpressionType.Equal] = "=", 33 | [ExpressionType.NotEqual] = "<>", 34 | [ExpressionType.GreaterThan] = ">", 35 | [ExpressionType.LessThan] = "<", 36 | [ExpressionType.GreaterThanOrEqual] = ">=", 37 | [ExpressionType.LessThanOrEqual] = "<=", 38 | [ExpressionType.And] = "&" 39 | }; 40 | 41 | private string[] Columns { get; } 42 | private int RowIndex { get; } 43 | 44 | public string Convert(Expression? expression) 45 | { 46 | if (expression == null) return string.Empty; 47 | return expression.NodeType == ExpressionType.Lambda 48 | ? InternalConvert((expression as LambdaExpression)?.Body) 49 | : string.Empty; 50 | } 51 | 52 | private static string ConvertConstant(Expression expression) 53 | { 54 | var exp = expression as ConstantExpression; 55 | return (exp?.Type == typeof(bool) ? exp.ToString().ToUpper() : exp?.ToString()) ?? string.Empty; 56 | } 57 | 58 | private string ConvertBinaryExpression(Expression expression) 59 | { 60 | if (!(expression is BinaryExpression binary)) return "null"; 61 | var symbol = $"unsupported binary symbol:{binary.NodeType}"; 62 | if (BinarySymbolDictionary.TryGetValue(binary.NodeType, out var value)) symbol = value; 63 | 64 | return $"{InternalConvert(binary.Left)}{symbol}{InternalConvert(binary.Right)}"; 65 | } 66 | 67 | private string ConvertCall(Expression expression) 68 | { 69 | if (!(expression is MethodCallExpression exp) || exp.Object == null) return "null"; 70 | if (exp.Method.Name == "get_Item" && 71 | (exp.Object.Type == typeof(ColumnCellDictionary) 72 | || exp.Object.Type == typeof(Dictionary) 73 | ) 74 | ) 75 | return exp.Arguments.Count == 2 76 | ? $"{GetColumn(exp.Arguments[0])}{InternalConvert(exp.Arguments[1])}" 77 | : $"{GetColumn(exp.Arguments[0])}{RowIndex + 1}"; 78 | 79 | if (exp.Object.Type == typeof(ColumnCellDictionary) && 80 | exp.Method.Name == nameof(ColumnCellDictionary.Matrix)) 81 | return 82 | $"{GetColumn(exp.Arguments[0])}{exp.Arguments[1]}:{GetColumn(exp.Arguments[2])}{exp.Arguments[3]}"; 83 | 84 | if (exp.Method.DeclaringType == typeof(DateTime)) 85 | { 86 | if (exp.Method.Name == nameof(DateTime.AddMonths)) 87 | return $"EDATE({InternalConvert(exp.Object)},{InternalConvert(exp.Arguments[0])})"; 88 | } 89 | else if (CallMethodTypes.Contains(exp.Method.DeclaringType)) 90 | { 91 | return 92 | $"{exp.Method.Name.ToUpper()}({string.Join(",", exp.Arguments.Select(c => InternalConvert(c)))})"; 93 | } 94 | 95 | return $"unspport call type={exp.Method.DeclaringType} name={exp.Method.Name}"; 96 | } 97 | 98 | private string ConvertMemberAccess(Expression expression) 99 | { 100 | var exp = expression as MemberExpression; 101 | var member = exp?.Member; 102 | if (member == null) return string.Empty; 103 | if (member.DeclaringType != typeof(DateTime)) 104 | return $"unspport member access type={member.DeclaringType} name={member.Name}"; 105 | switch (member.Name) 106 | { 107 | case "Now": 108 | return "NOW()"; 109 | case "Year": 110 | return $"YEAR({InternalConvert(exp.Expression)})"; 111 | case "Month": 112 | return $"MONTH({InternalConvert(exp.Expression)})"; 113 | case "Day": 114 | return $"DAY({InternalConvert(exp.Expression)})"; 115 | default: 116 | return $"unsupported member access type={member.DeclaringType} name={member.Name}"; 117 | } 118 | } 119 | 120 | private string ConvertUnaryExpression(Expression expression) 121 | { 122 | if (!(expression is UnaryExpression unary)) return "null"; 123 | var symbol = unary.NodeType == ExpressionType.Negate ? "-" : "unsupported unary symbol"; 124 | return $"{symbol}{InternalConvert(unary.Operand)}"; 125 | } 126 | 127 | private string GetColumn(Expression exp) 128 | { 129 | if (exp is not ConstantExpression constant) return "null"; 130 | var key = constant.Value?.ToString(); 131 | var columnIndex = Array.IndexOf(Columns, key); 132 | return columnIndex == -1 ? $"ERROR key:{key}" : ExcelColumnNameParser.Parse(columnIndex); 133 | } 134 | 135 | private string InternalConvert(params Expression?[] expressions) 136 | { 137 | var expression = expressions[0]; 138 | if (expression == null) return ""; 139 | switch (expression.NodeType) 140 | { 141 | case ExpressionType.Convert: 142 | return InternalConvert((expression as UnaryExpression)?.Operand); 143 | case ExpressionType.Call: 144 | return ConvertCall(expression); 145 | case ExpressionType.MemberAccess: 146 | return ConvertMemberAccess(expression); 147 | case ExpressionType.Constant: 148 | return ConvertConstant(expression); 149 | } 150 | 151 | switch (expression) 152 | { 153 | case BinaryExpression _: 154 | return ConvertBinaryExpression(expression); 155 | case UnaryExpression _: 156 | return ConvertUnaryExpression(expression); 157 | } 158 | 159 | if (expression.NodeType != ExpressionType.NewArrayInit) return $"unsupported type {expressions[0]?.NodeType}"; 160 | if (expression is not NewArrayExpression exp) return "null"; 161 | return string.Join(",", exp.Expressions.Select(c => InternalConvert(c))); 162 | } 163 | } -------------------------------------------------------------------------------- /DateTimeFormats.md: -------------------------------------------------------------------------------- 1 | # Date/Time Format Support 2 | 3 | Excel2Object now supports a comprehensive set of date and time formats for Excel import operations. This document lists all supported formats and provides usage examples. 4 | 5 | ## Supported Date/Time Formats 6 | 7 | ### ISO 8601 Formats 8 | - `yyyy-MM-ddTHH:mm:ss` - Standard ISO 8601 format (e.g., "2023-07-31T14:30:45") 9 | - `yyyy-MM-ddTHH:mm:ssZ` - ISO 8601 with UTC timezone (e.g., "2023-07-31T14:30:45Z") 10 | - `yyyy-MM-ddTHH:mm:ss.fff` - ISO 8601 with milliseconds (e.g., "2023-07-31T14:30:45.123") 11 | - `yyyy-MM-ddTHH:mm:ss.fffZ` - ISO 8601 with milliseconds and UTC (e.g., "2023-07-31T14:30:45.123Z") 12 | - `yyyy-MM-dd HH:mm:ss` - ISO 8601 with space separator (e.g., "2023-07-31 14:30:45") 13 | - `yyyy-MM-dd HH:mm` - ISO 8601 without seconds (e.g., "2023-07-31 14:30") 14 | 15 | ### Date Formats with Various Separators 16 | 17 | #### Dash Separator (-) 18 | - `yyyy-MM-dd` - Year-first format (e.g., "2023-12-25") 19 | - `dd-MM-yyyy` - Day-first format (European style, e.g., "25-12-2023") 20 | - `MM-dd-yyyy` - Month-first format (US style, e.g., "12-25-2023") 21 | 22 | #### Slash Separator (/) 23 | - `yyyy/MM/dd` - Year-first format (e.g., "2023/12/25") 24 | - `dd/MM/yyyy` - Day-first format (European style, e.g., "25/12/2023") 25 | - `MM/dd/yyyy` - Month-first format (US style, e.g., "12/25/2023") 26 | 27 | #### Dot Separator (.) 28 | - `yyyy.MM.dd` - Year-first format (e.g., "2023.12.25") 29 | - `dd.MM.yyyy` - Day-first format (European style, e.g., "25.12.2023") 30 | - `MM.dd.yyyy` - Month-first format (US style, e.g., "12.25.2023") 31 | 32 | ### Date with Time (12-Hour Format with AM/PM) 33 | - `yyyy-MM-dd hh:mm:ss tt` - Full date/time with AM/PM (e.g., "2023-07-31 02:30:45 PM") 34 | - `yyyy-MM-dd hh:mm tt` - Date/time without seconds (e.g., "2023-07-31 02:30 PM") 35 | - `dd-MM-yyyy hh:mm:ss tt` - European date with AM/PM (e.g., "31-07-2023 02:30:45 PM") 36 | - `dd/MM/yyyy hh:mm:ss tt` - European date with slash and AM/PM (e.g., "31/07/2023 02:30:45 PM") 37 | - `MM-dd-yyyy hh:mm:ss tt` - US date with AM/PM (e.g., "07-31-2023 02:30:45 PM") 38 | - `MM/dd/yyyy hh:mm:ss tt` - US date with slash and AM/PM (e.g., "07/31/2023 02:30:45 PM") 39 | 40 | ### Date with Time (24-Hour Format) 41 | - `dd-MM-yyyy HH:mm:ss` - European date with 24-hour time (e.g., "31-07-2023 14:30:45") 42 | - `dd/MM/yyyy HH:mm:ss` - European date with slash and 24-hour time (e.g., "31/07/2023 14:30:45") 43 | - `MM-dd-yyyy HH:mm:ss` - US date with 24-hour time (e.g., "07-31-2023 14:30:45") 44 | - `MM/dd/yyyy HH:mm:ss` - US date with slash and 24-hour time (e.g., "07/31/2023 14:30:45") 45 | - `dd-MM-yyyy HH:mm` - European date without seconds (e.g., "31-07-2023 14:30") 46 | - `dd/MM/yyyy HH:mm` - European date with slash, no seconds (e.g., "31/07/2023 14:30") 47 | - `MM-dd-yyyy HH:mm` - US date without seconds (e.g., "07-31-2023 14:30") 48 | - `MM/dd/yyyy HH:mm` - US date with slash, no seconds (e.g., "07/31/2023 14:30") 49 | 50 | ### Short Date Formats (Single-Digit Month/Day) 51 | - `yyyy/M/d` - Year-first with single digits (e.g., "2023/3/5") 52 | - `yyyy-M-d` - Year-first with dash and single digits (e.g., "2023-3-5") 53 | - `d/M/yyyy` - Day-first with single digits (e.g., "5/3/2023") 54 | - `d-M-yyyy` - Day-first with dash and single digits (e.g., "5-3-2023") 55 | - `M/d/yyyy` - Month-first with single digits (e.g., "3/5/2023") 56 | - `M-d-yyyy` - Month-first with dash and single digits (e.g., "3-5-2023") 57 | 58 | ### Time-Only Formats (24-Hour) 59 | When only time is provided, it will be combined with today's date. 60 | - `HH:mm:ss` - 24-hour time with seconds (e.g., "14:30:45") 61 | - `HH:mm` - 24-hour time without seconds (e.g., "14:30") 62 | - `H:mm:ss` - Single-digit hour allowed (e.g., "9:30:45") 63 | - `H:mm` - Single-digit hour without seconds (e.g., "9:30") 64 | 65 | ### Time-Only Formats (12-Hour with AM/PM) 66 | When only time is provided, it will be combined with today's date. 67 | - `hh:mm:ss tt` - 12-hour time with seconds and AM/PM (e.g., "02:30:45 PM") 68 | - `hh:mm tt` - 12-hour time without seconds (e.g., "02:30 PM") 69 | - `h:mm:ss tt` - Single-digit hour with AM/PM (e.g., "2:30:45 PM") 70 | - `h:mm tt` - Single-digit hour without seconds (e.g., "2:30 PM") 71 | 72 | ### Chinese Date Formats (Legacy Support) 73 | Excel2Object continues to support traditional Chinese date formats: 74 | - `yyyy年MM月dd日` - Full Chinese date (e.g., "2023年07月31日") 75 | - `yyyy年MM月` - Year and month only (e.g., "2023年07月") 76 | - `yyyy年` - Year only (e.g., "2023年") 77 | 78 | ## Usage Examples 79 | 80 | ### Basic Import with DateTime 81 | 82 | ```csharp 83 | using Chsword.Excel2Object; 84 | 85 | public class Person 86 | { 87 | [ExcelTitle("姓名")] 88 | public string Name { get; set; } 89 | 90 | [ExcelTitle("出生日期")] 91 | public DateTime? Birthday { get; set; } 92 | 93 | [ExcelTitle("创建时间")] 94 | public DateTime? CreateTime { get; set; } 95 | } 96 | 97 | var importer = new ExcelImporter(); 98 | var persons = importer.ExcelToObject("data.xlsx"); 99 | ``` 100 | 101 | ### Custom Date Format 102 | 103 | ```csharp 104 | public class Employee 105 | { 106 | [ExcelTitle("姓名")] 107 | public string Name { get; set; } 108 | 109 | [ExcelColumn("入职日期", Format = "yyyy-MM-dd HH:mm:ss")] 110 | public DateTime HireDate { get; set; } 111 | 112 | [ExcelColumn("离职日期", Format = "yyyy-MM-dd HH:mm:ss")] 113 | public DateTime? TerminationDate { get; set; } 114 | } 115 | ``` 116 | 117 | ### Supported Excel Cell Types 118 | 119 | The library handles dates stored in Excel in two ways: 120 | 121 | 1. **Numeric Cell Type**: Excel stores dates as numeric values (days since 1900-01-01). The library automatically converts these using NPOI's `DateCellValue` property. 122 | 123 | 2. **String Cell Type**: When dates are stored as text in Excel, the library attempts to parse them using the comprehensive format list above. 124 | 125 | ## Regional Settings 126 | 127 | The library attempts to parse dates using both `CultureInfo.InvariantCulture` and `CultureInfo.CurrentCulture` to handle regional differences. This means: 128 | 129 | - US format dates (MM/dd/yyyy) will be correctly parsed 130 | - European format dates (dd/MM/yyyy) will be correctly parsed 131 | - ISO 8601 formats will always be parsed correctly regardless of regional settings 132 | 133 | ## Best Practices 134 | 135 | 1. **Use Nullable DateTime**: For optional date fields, use `DateTime?` to handle empty cells gracefully. 136 | 137 | 2. **Prefer Numeric Date Storage**: When possible, store dates in Excel as actual date values (numeric format) rather than text, as this is more reliable. 138 | 139 | 3. **Specify Format for Export**: When exporting to Excel with custom date formats, use the `Format` parameter in `ExcelColumnAttribute`: 140 | ```csharp 141 | [ExcelColumn("日期", Format = "yyyy-MM-dd HH:mm:ss")] 142 | public DateTime MyDate { get; set; } 143 | ``` 144 | 145 | 4. **Use ISO 8601 for Text Dates**: If dates must be stored as text in Excel, use ISO 8601 format (yyyy-MM-dd or yyyy-MM-ddTHH:mm:ss) for maximum compatibility. 146 | 147 | 5. **Avoid Ambiguous Formats**: Formats like "01/02/2023" can be interpreted as either January 2nd or February 1st depending on regional settings. Use year-first formats or be explicit about the ordering. 148 | 149 | ## Edge Cases Handled 150 | 151 | - **Leap Years**: The library correctly handles February 29 in leap years 152 | - **Time Zones**: ISO 8601 formats with 'Z' suffix are supported 153 | - **Milliseconds**: Formats with milliseconds (.fff) are supported 154 | - **Midnight/Noon**: Times at 00:00:00 and 12:00:00 are handled correctly 155 | - **Time-Only Values**: When only time is provided (no date), it's combined with the current date 156 | - **Empty Cells**: Empty cells are handled as null for nullable DateTime fields 157 | 158 | ## Performance Considerations 159 | 160 | The library tries multiple parsing strategies in order: 161 | 1. Standard `DateTime.TryParse` (fastest) 162 | 2. Culture-specific exact format parsing with InvariantCulture 163 | 3. Culture-specific exact format parsing with CurrentCulture 164 | 4. Special handling for time-only formats 165 | 5. Fallback strategies for partial dates 166 | 167 | This ensures maximum compatibility while maintaining reasonable performance. 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Excel2Object 2 | 3 | [![install from nuget](http://img.shields.io/nuget/v/Chsword.Excel2Object.svg?style=flat-square)](https://www.nuget.org/packages/Chsword.Excel2Object) 4 | [![release](https://img.shields.io/github/release/chsword/Excel2Object.svg?style=flat-square)](https://github.com/chsword/Excel2Object/releases) 5 | [![.NET CI](https://github.com/chsword/Excel2Object/actions/workflows/dotnet-ci.yml/badge.svg)](https://github.com/chsword/Excel2Object/actions/workflows/dotnet-ci.yml) 6 | [![CodeFactor](https://www.codefactor.io/repository/github/chsword/excel2object/badge)](https://www.codefactor.io/repository/github/chsword/excel2object) 7 | 8 | Excel 与 .NET 对象互相转换 / Excel convert to .NET Object and vice versa. 9 | 10 | [English](README_EN.md) | 中文 11 | 12 | - [Top](#excel2object) 13 | - [安装 NuGet](#安装-nuget) 14 | - [发布说明和路线图](#发布说明和路线图) 15 | - [示例代码](#示例代码) 16 | - [文档](#文档) 17 | - [开发和发布](#开发和发布) 18 | - [贡献者](#贡献者) 19 | - [参考](#参考) 20 | 21 | ## 平台支持 22 | 23 | [![.NET 4.7.2 +](https://img.shields.io/badge/-4.7.2%2B-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 24 | [![.NET Standard 2.0](https://img.shields.io/badge/-standard2.0-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 25 | [![.NET Standard 2.1](https://img.shields.io/badge/-standard2.1-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 26 | [![.NET 6.0](https://img.shields.io/badge/-6.0-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 27 | [![.NET 8.0](https://img.shields.io/badge/-8.0-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 28 | 29 | ## 安装 NuGet 30 | 31 | ``` powershell 32 | PM> Install-Package Chsword.Excel2Object 33 | ``` 34 | 35 | 或使用 .NET CLI: 36 | ``` bash 37 | dotnet add package Chsword.Excel2Object 38 | ``` 39 | 40 | ## 发布说明和路线图 41 | 42 | ### 暂不支持的特性 43 | 44 | - [ ] CLI 工具 45 | - [x] 支持自动列宽 ✅ **v2.0.1 新增** 46 | - [x] 支持 Excel 日期/日期时间/时间格式 ✅ **v2.0.2 新增** - 查看 [DateTimeFormats.md](DateTimeFormats.md) 47 | 48 | ### 发布说明 49 | 50 | * **2025.10.11** - v2.0.2 51 | - [x] ✨ **新增:** 全面的日期/时间格式支持(56 种格式)- 查看 [DateTimeFormats.md](DateTimeFormats.md) 52 | - ISO 8601 格式(支持时区和毫秒) 53 | - 多种日期分隔符(横线、斜线、点号) 54 | - 12 小时制和 24 小时制时间格式 55 | - 仅时间格式 56 | - 区域格式支持(美国、欧洲等) 57 | - 向后兼容现有的中文日期格式(年月日) 58 | - [x] ✨ **更新:** NPOI 到 2.7.5 59 | - [x] ✨ **更新:** SixLabors.ImageSharp 到 3.1.11(修复安全漏洞) 60 | 61 | * **2025.07.23** - v2.0.1 62 | - [x] ✨ **新增:** 基于内容的自动列宽调整 63 | - 自动计算最优列宽 64 | - 支持最小和最大宽度限制 65 | - 正确处理中文/Unicode 字符 66 | - 可通过 `ExcelExporterOptions` 配置 67 | 68 | * **2024.10.21** 69 | - [x] 更新 SixLabors.ImageSharp 到 2.1.9 70 | - [x] 测试 .NET 8.0 71 | 72 | * **2024.05.10** 73 | - [x] 支持 .NET 8.0 / .NET 6.0 / .NET Standard 2.1 / .NET Standard 2.0 / .NET Framework 4.7.2 74 | - [x] 清理已弃用的库 75 | 76 | * **2023.11.02** 77 | - [x] 支持列标题映射 - [Issue39DynamicMappingTitle.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Issue39DynamicMappingTitle.cs) 78 | 79 | * **2023.07.31** 80 | - [x] 支持 DateTime 和 Nullable 格式,如 `[ExcelColumn("Title",Format="yyyy-MM-dd HH:mm:ss")]` 81 | 82 | * **2023.03.26** 83 | - [x] 支持列标题中的特殊符号 #37 - [Issue37SpecialCharTest.cs](https://github.com/chsword/Excel2Object/commit/273122275e724367bb6154e03df61702fcec81b3#diff-5f0f5f7558bf7d4207cfa752a4506c4df89d9b491e2501e4862aff0c2276bd61) 84 | 85 | * **2023.02.20** 86 | - [x] 支持平台:.NET Standard 2.0/2.1、.NET 6.0、.NET Framework 4.7.2 87 | 88 | * **2022.03.19** 89 | - [x] 支持 ExcelImporterOptions,跳过行 - [Issue32SkipLineImport.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Issue32SkipLineImport.cs) 90 | - [x] 修复超类属性 bug - [Issue31SuperClass.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Issue31SuperClass.cs) 91 | 92 | * **2021.11.4** 93 | - [x] 多 sheet 支持 - [Pr28MultipleSheetTest.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Pr28MultipleSheetTest.cs) 94 | 95 | * **2021.10.23** 96 | - [x] 修复 Nullable DateTime bug @SunBrook 97 | 98 | * **2021.10.22** 99 | - [x] 支持 Nullable 类型 - [Pr24NullableTest.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Pr24NullableTest.cs) @SunBrook 100 | 101 | * **2021.5.28** 102 | - [x] 支持表头和单元格样式,新增列的 [ExcelColumnAttribute] 103 | - [x] 支持公式 - [ExcelFunctions.md](./ExcelFunctions.md) 104 | 105 | ```C# 106 | var list = new List 107 | { 108 | new Pr20Model 109 | { 110 | Fullname = "AAA", Mobile = "123456798123" 111 | }, 112 | new Pr20Model 113 | { 114 | Fullname = "BBB", Mobile = "234" 115 | } 116 | }; 117 | var bytes = ExcelHelper.ObjectToExcelBytes(list, ExcelType.Xlsx); 118 | // model 119 | [ExcelTitle("SheetX")] 120 | public class Pr20Model 121 | { 122 | [ExcelColumn("Full name", CellFontColor = ExcelStyleColor.Red)] 123 | public string Fullname { get; set; } 124 | 125 | [ExcelColumn("Phone Number", 126 | HeaderFontFamily = "Normal", 127 | HeaderBold = true, 128 | HeaderFontHeight = 30, 129 | HeaderItalic = true, 130 | HeaderFontColor = ExcelStyleColor.Blue, 131 | HeaderUnderline = true, 132 | HeaderAlignment = HorizontalAlignment.Right, 133 | //cell 134 | CellAlignment = HorizontalAlignment.Justify 135 | )] 136 | public string Mobile { get; set; } 137 | } 138 | ``` 139 | 140 | * **v2.0.0.113** 141 | ``` 142 | convert project to netstandard2.0 and .net452 143 | fixbug #12 #13 144 | ``` 145 | 146 | * **v1.0.0.80** 147 | 148 | - [x] support simple formula 149 | - [x] support standard excel model 150 | - [x] excel & JSON convert 151 | - [x] excel & Dictionary convert 152 | 153 | ``` 154 | Support Uri to a hyperlink cell 155 | And also support text cell to Uri Type 156 | ``` 157 | 158 | * **v1.0.0.43** 159 | ``` 160 | Support xlsx [thanks Soar360] 161 | Support complex Boolean type 162 | ``` 163 | 164 | * **v1.0.0.36** 165 | ``` 166 | Add ExcelToObject(bytes) 167 | ``` 168 | 169 | 170 | ## 示例代码 171 | 172 | ### 定义模型 173 | 174 | ``` csharp 175 | public class ReportModel 176 | { 177 | [Excel("My Title", Order=1)] 178 | public string Title { get; set; } 179 | 180 | [Excel("User Name", Order=2)] 181 | public string Name { get; set; } 182 | } 183 | ``` 184 | 185 | ### 创建模型列表 186 | 187 | ``` csharp 188 | var models = new List 189 | { 190 | new ReportModel{Name="a", Title="b"}, 191 | new ReportModel{Name="c", Title="d"}, 192 | new ReportModel{Name="f", Title="e"} 193 | }; 194 | ``` 195 | 196 | ### 对象转 Excel 文件 197 | 198 | ``` csharp 199 | var exporter = new ExcelExporter(); 200 | var bytes = exporter.ObjectToExcelBytes(models); 201 | File.WriteAllBytes("C:\\demo.xls", bytes); 202 | ``` 203 | 204 | ### Excel 文件转对象 205 | 206 | ``` csharp 207 | var importer = new ExcelImporter(); 208 | IEnumerable result = importer.ExcelToObject("c:\\demo.xls"); 209 | 210 | // 也可以直接使用字节数组 211 | // IEnumerable result = importer.ExcelToObject(bytes); 212 | ``` 213 | 214 | ### 自动列宽(新特性) 215 | 216 | ``` csharp 217 | // 启用自动列宽调整 218 | var bytes = ExcelHelper.ObjectToExcelBytes(models, options => 219 | { 220 | options.ExcelType = ExcelType.Xlsx; 221 | options.AutoColumnWidth = true; // 启用自动宽度 222 | options.MinColumnWidth = 8; // 最小宽度(字符) 223 | options.MaxColumnWidth = 50; // 最大宽度(字符) 224 | options.DefaultColumnWidth = 16; // 禁用自动时的默认宽度 225 | }); 226 | ``` 227 | 228 | ### 在 ASP.NET MVC 中使用 229 | 230 | 在 ASP.NET MVC 模型中,`DisplayAttribute` 可以像 `ExcelTitleAttribute` 一样被支持。 231 | 232 | ## 文档 233 | 234 | 更多信息请访问:http://www.cnblogs.com/chsword/p/excel2object.html 235 | 236 | ## 开发和发布 237 | 238 | - **[自动化发布脚本 `release.ps1`](release.ps1)** - 一键完成版本更新、提交和 Tag 创建的 PowerShell 脚本 239 | - **[自动化发布系统说明](RELEASE_AUTOMATION.md)** - 包含 Copilot 指令、版本管理和自动发布流程的完整说明 240 | - **[版本管理规范](.github/VERSIONING.md)** - 语义化版本规范和版本号递增规则 241 | - **[发布流程指南](.github/RELEASE_GUIDE.md)** - 详细的发布步骤和故障排查指南 242 | - **[Copilot 使用说明](.github/copilot-instructions.md)** - GitHub Copilot 开发指导和项目规范 243 | 244 | ### 快速发布新版本 245 | 246 | 使用自动化脚本一键发布: 247 | 248 | ```powershell 249 | # Windows 250 | .\release.ps1 -Version 2.0.4 251 | 252 | # Linux/macOS (需要 PowerShell Core) 253 | pwsh ./release.ps1 -Version 2.0.4 254 | ``` 255 | 256 | 详细说明请参阅 [RELEASE_AUTOMATION.md](RELEASE_AUTOMATION.md)。 257 | 258 | ## 贡献者 259 | 260 | [![Contributors](https://contrib.rocks/image?repo=chsword/Excel2Object)](https://github.com/chsword/Excel2Object/graphs/contributors) 261 | 262 | ## 参考 263 | 264 | - https://github.com/tonyqus/npoi 265 | - https://github.com/chsword/ctrc 266 | 267 | ## 许可证 268 | 269 | 本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。 270 | -------------------------------------------------------------------------------- /release.ps1.backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | <# 3 | .SYNOPSIS 4 | 鑷姩鍖栧彂甯冭剼鏈?- 鏇存柊鐗堟湰鍙枫€佹彁浜や唬鐮併€佸垱寤哄苟鎺ㄩ€乀ag 5 | Release automation script - Update version, commit changes, create and push tag 6 | 7 | .DESCRIPTION 8 | 璇ヨ剼鏈嚜鍔ㄦ墽琛屼互涓嬫搷浣滐細 9 | 1. 鏇存柊 Chsword.Excel2Object.csproj 涓殑鐗堟湰鍙? 10 | 2. 鎻愪氦浠g爜鍒?Git 浠撳簱 11 | 3. 鍒涘缓 Git Tag (鏍煎紡: v{version}) 12 | 4. 鎺ㄩ€佷唬鐮佸拰 Tag 鍒拌繙绋嬩粨搴? 13 | 14 | This script automates the following operations: 15 | 1. Update version in Chsword.Excel2Object.csproj 16 | 2. Commit changes to Git repository 17 | 3. Create Git Tag (format: v{version}) 18 | 4. Push code and tag to remote repository 19 | 20 | .PARAMETER Version 21 | 鏂扮増鏈彿锛屾牸寮忥細涓荤増鏈?娆$増鏈?淇鐗?(渚嬪: 2.0.3) 22 | New version number, format: Major.Minor.Patch (e.g., 2.0.3) 23 | 24 | .PARAMETER SkipPush 25 | 浠呭垱寤烘湰鍦版彁浜ゅ拰鏍囩锛屼笉鎺ㄩ€佸埌杩滅▼浠撳簱 26 | Only create local commits and tags, do not push to remote 27 | 28 | .PARAMETER Force 29 | 寮哄埗鎵ц锛岃烦杩囩‘璁ゆ彁绀? 30 | Force execution, skip confirmation prompts 31 | 32 | .EXAMPLE 33 | ./release.ps1 -Version 2.0.3 34 | 鏇存柊鐗堟湰鍒?2.0.3锛屾彁浜ゅ苟鎺ㄩ€? 35 | 36 | .EXAMPLE 37 | ./release.ps1 -Version 2.1.0 -SkipPush 38 | 鏇存柊鐗堟湰鍒?2.1.0锛屼粎鏈湴鎻愪氦锛屼笉鎺ㄩ€? 39 | 40 | .EXAMPLE 41 | ./release.ps1 -Version 3.0.0 -Force 42 | 寮哄埗鏇存柊鐗堟湰鍒?3.0.0锛岃烦杩囩‘璁ゆ彁绀? 43 | #> 44 | 45 | param( 46 | [Parameter(Mandatory = $true, HelpMessage = "鐗堟湰鍙?(渚嬪: 2.0.3)")] 47 | [string]$Version, 48 | 49 | [Parameter(Mandatory = $false)] 50 | [switch]$SkipPush, 51 | 52 | [Parameter(Mandatory = $false)] 53 | [switch]$Force 54 | ) 55 | 56 | # 璁剧疆閿欒澶勭悊 57 | $ErrorActionPreference = "Stop" 58 | 59 | # 棰滆壊杈撳嚭鍑芥暟 60 | function Write-ColorOutput { 61 | param( 62 | [string]$Message, 63 | [string]$Color = "White" 64 | ) 65 | Write-Host $Message -ForegroundColor $Color 66 | } 67 | 68 | function Write-Success { 69 | param([string]$Message) 70 | Write-ColorOutput "鉁?$Message" "Green" 71 | } 72 | 73 | function Write-Error { 74 | param([string]$Message) 75 | Write-ColorOutput "鉁?$Message" "Red" 76 | } 77 | 78 | function Write-Info { 79 | param([string]$Message) 80 | Write-ColorOutput "鈩?$Message" "Cyan" 81 | } 82 | 83 | function Write-Warning { 84 | param([string]$Message) 85 | Write-ColorOutput "鈿?$Message" "Yellow" 86 | } 87 | 88 | # 楠岃瘉鐗堟湰鍙锋牸寮?(璇箟鍖栫増鏈? 89 | function Test-VersionFormat { 90 | param([string]$Ver) 91 | 92 | if ($Ver -match '^\d+\.\d+\.\d+$') { 93 | return $true 94 | } 95 | return $false 96 | } 97 | 98 | # 鎻愬彇褰撳墠鐗堟湰鍙? 99 | function Get-CurrentVersion { 100 | $csprojPath = "Chsword.Excel2Object/Chsword.Excel2Object.csproj" 101 | 102 | if (-not (Test-Path $csprojPath)) { 103 | throw "鎵句笉鍒伴」鐩枃浠? $csprojPath" 104 | } 105 | 106 | $content = Get-Content $csprojPath -Raw 107 | if ($content -match '([^<]+)') { 108 | return $Matches[1] 109 | } 110 | 111 | throw "鏃犳硶浠庨」鐩枃浠朵腑鎻愬彇鐗堟湰鍙? 112 | } 113 | 114 | # 鏇存柊鐗堟湰鍙? 115 | function Update-Version { 116 | param( 117 | [string]$NewVersion 118 | ) 119 | 120 | $csprojPath = "Chsword.Excel2Object/Chsword.Excel2Object.csproj" 121 | 122 | $content = Get-Content $csprojPath -Raw 123 | $newContent = $content -replace '[^<]+', "$NewVersion" 124 | 125 | Set-Content -Path $csprojPath -Value $newContent -NoNewline 126 | 127 | Write-Success "鐗堟湰鍙峰凡鏇存柊涓? $NewVersion" 128 | } 129 | 130 | # 妫€鏌?Git 鐘舵€? 131 | function Test-GitClean { 132 | $status = git status --porcelain 133 | if ($status) { 134 | return $false 135 | } 136 | return $true 137 | } 138 | 139 | # 妫€鏌ユ槸鍚﹀湪 Git 浠撳簱涓? 140 | function Test-GitRepository { 141 | try { 142 | git rev-parse --git-dir 2>&1 | Out-Null 143 | return $LASTEXITCODE -eq 0 144 | } 145 | catch { 146 | return $false 147 | } 148 | } 149 | 150 | # 妫€鏌?Tag 鏄惁宸插瓨鍦? 151 | function Test-TagExists { 152 | param([string]$TagName) 153 | 154 | $tags = git tag -l $TagName 155 | return ($tags -eq $TagName) 156 | } 157 | 158 | # 涓绘祦绋? 159 | try { 160 | Write-Info "===== Excel2Object 鑷姩鍖栧彂甯冭剼鏈?=====" 161 | Write-Info "" 162 | 163 | # 楠岃瘉鐗堟湰鍙锋牸寮? 164 | if (-not (Test-VersionFormat -Ver $Version)) { 165 | Write-Error "鐗堟湰鍙锋牸寮忛敊璇紒璇蜂娇鐢ㄨ涔夊寲鐗堟湰鏍煎紡 (渚嬪: 2.0.3)" 166 | exit 1 167 | } 168 | 169 | # 妫€鏌ユ槸鍚﹀湪 Git 浠撳簱涓? 170 | if (-not (Test-GitRepository)) { 171 | Write-Error "褰撳墠鐩綍涓嶆槸 Git 浠撳簱锛? 172 | exit 1 173 | } 174 | 175 | # 鑾峰彇褰撳墠鐗堟湰 176 | $currentVersion = Get-CurrentVersion 177 | Write-Info "褰撳墠鐗堟湰: $currentVersion" 178 | Write-Info "鐩爣鐗堟湰: $Version" 179 | Write-Info "" 180 | 181 | # 妫€鏌ョ増鏈槸鍚︾浉鍚? 182 | if ($currentVersion -eq $Version) { 183 | Write-Warning "鐩爣鐗堟湰涓庡綋鍓嶇増鏈浉鍚岋紝鏃犻渶鏇存柊銆? 184 | if (-not $Force) { 185 | $continue = Read-Host "鏄惁缁х画锛?y/N)" 186 | if ($continue -ne 'y' -and $continue -ne 'Y') { 187 | Write-Info "鎿嶄綔宸插彇娑堛€? 188 | exit 0 189 | } 190 | } 191 | } 192 | 193 | # 妫€鏌ュ伐浣滅洰褰曟槸鍚﹀共鍑€ 194 | if (-not (Test-GitClean)) { 195 | Write-Warning "宸ヤ綔鐩綍鏈夋湭鎻愪氦鐨勬洿鏀癸紒" 196 | git status --short 197 | Write-Info "" 198 | 199 | if (-not $Force) { 200 | $continue = Read-Host "鏄惁缁х画锛熻繖灏嗘彁浜ゆ墍鏈夋洿鏀?(y/N)" 201 | if ($continue -ne 'y' -and $continue -ne 'Y') { 202 | Write-Info "鎿嶄綔宸插彇娑堛€? 203 | exit 0 204 | } 205 | } 206 | } 207 | 208 | # 妫€鏌?Tag 鏄惁宸插瓨鍦? 209 | $tagName = "v$Version" 210 | if (Test-TagExists -TagName $tagName) { 211 | Write-Error "Tag '$tagName' 宸插瓨鍦紒" 212 | Write-Info "璇蜂娇鐢ㄤ笉鍚岀殑鐗堟湰鍙锋垨鍒犻櫎鐜版湁 Tag锛? 213 | Write-Info " git tag -d $tagName" 214 | Write-Info " git push origin :refs/tags/$tagName" 215 | exit 1 216 | } 217 | 218 | # 纭鎿嶄綔 219 | if (-not $Force) { 220 | Write-Info "" 221 | Write-Warning "鍗冲皢鎵ц浠ヤ笅鎿嶄綔锛? 222 | Write-Info " 1. 鏇存柊鐗堟湰鍙? $currentVersion 鈫?$Version" 223 | Write-Info " 2. 鎻愪氦鏇存敼: 'chore: bump version to $Version'" 224 | Write-Info " 3. 鍒涘缓 Tag: $tagName" 225 | if (-not $SkipPush) { 226 | Write-Info " 4. 鎺ㄩ€佸埌杩滅▼浠撳簱" 227 | } 228 | Write-Info "" 229 | 230 | $confirm = Read-Host "纭鎵ц锛?y/N)" 231 | if ($confirm -ne 'y' -and $confirm -ne 'Y') { 232 | Write-Info "鎿嶄綔宸插彇娑堛€? 233 | exit 0 234 | } 235 | Write-Info "" 236 | } 237 | 238 | # 姝ラ 1: 鏇存柊鐗堟湰鍙? 239 | Write-Info "姝ラ 1/4: 鏇存柊鐗堟湰鍙?.." 240 | Update-Version -NewVersion $Version 241 | 242 | # 姝ラ 2: 鎻愪氦鏇存敼 243 | Write-Info "姝ラ 2/4: 鎻愪氦鏇存敼..." 244 | git add . 245 | git commit -m "chore: bump version to $Version" 246 | Write-Success "浠g爜宸叉彁浜? 247 | 248 | # 姝ラ 3: 鍒涘缓 Tag 249 | Write-Info "姝ラ 3/4: 鍒涘缓 Tag..." 250 | git tag $tagName 251 | Write-Success "Tag '$tagName' 宸插垱寤? 252 | 253 | # 姝ラ 4: 鎺ㄩ€佸埌杩滅▼ 254 | if (-not $SkipPush) { 255 | Write-Info "姝ラ 4/4: 鎺ㄩ€佸埌杩滅▼浠撳簱..." 256 | 257 | # 鑾峰彇褰撳墠鍒嗘敮 258 | $currentBranch = git rev-parse --abbrev-ref HEAD 259 | 260 | # 鎺ㄩ€佷唬鐮? 261 | Write-Info "鎺ㄩ€佸垎鏀?'$currentBranch'..." 262 | git push origin $currentBranch 263 | Write-Success "浠g爜宸叉帹閫佸埌 origin/$currentBranch" 264 | 265 | # 鎺ㄩ€?Tag 266 | Write-Info "鎺ㄩ€?Tag '$tagName'..." 267 | git push origin $tagName 268 | Write-Success "Tag 宸叉帹閫? 269 | } 270 | else { 271 | Write-Info "姝ラ 4/4: 璺宠繃鎺ㄩ€?(浣跨敤浜?-SkipPush 鍙傛暟)" 272 | Write-Warning "璇锋墜鍔ㄦ帹閫佷唬鐮佸拰 Tag锛? 273 | Write-Info " git push origin $(git rev-parse --abbrev-ref HEAD)" 274 | Write-Info " git push origin $tagName" 275 | } 276 | 277 | # 瀹屾垚 278 | Write-Info "" 279 | Write-Success "===== 鍙戝竷娴佺▼瀹屾垚 =====" 280 | Write-Info "" 281 | Write-Info "鐗堟湰 $Version 宸插噯澶囧氨缁紒" 282 | 283 | if (-not $SkipPush) { 284 | Write-Info "" 285 | Write-Info "GitHub Actions 灏嗚嚜鍔ㄥ紑濮嬫瀯寤哄拰鍙戝竷娴佺▼锛? 286 | Write-Info " 鈫?https://github.com/chsword/Excel2Object/actions" 287 | Write-Info "" 288 | Write-Info "棰勮 5-10 鍒嗛挓鍚庡彲鍦ㄤ互涓嬩綅缃煡鐪嬪彂甯冪粨鏋滐細" 289 | Write-Info " 鈫?NuGet: https://www.nuget.org/packages/Chsword.Excel2Object" 290 | Write-Info " 鈫?GitHub: https://github.com/chsword/Excel2Object/releases" 291 | } 292 | } 293 | catch { 294 | Write-Error "鍙戠敓閿欒: $_" 295 | Write-Info "" 296 | Write-Info "濡傞渶甯姪锛岃鏌ョ湅鏂囨。锛? 297 | Write-Info " 鈫?RELEASE_AUTOMATION.md" 298 | exit 1 299 | } 300 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release and Publish 2 | 3 | # 触发条件:当推送 v* 格式的 tag 时触发 4 | # Trigger: When a tag matching v* is pushed 5 | on: 6 | push: 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | # 验证版本号 12 | validate-version: 13 | name: Validate Version 14 | runs-on: ubuntu-latest 15 | outputs: 16 | version: ${{ steps.extract.outputs.version }} 17 | version_without_v: ${{ steps.extract.outputs.version_without_v }} 18 | 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 # 获取完整历史以便版本比较 24 | 25 | - name: Extract version from tag 26 | id: extract 27 | run: | 28 | # 从 tag 中提取版本号(去掉 v 前缀) 29 | TAG_NAME=${GITHUB_REF#refs/tags/} 30 | VERSION=${TAG_NAME#v} 31 | echo "version=$TAG_NAME" >> $GITHUB_OUTPUT 32 | echo "version_without_v=$VERSION" >> $GITHUB_OUTPUT 33 | echo "Tag: $TAG_NAME" 34 | echo "Version: $VERSION" 35 | 36 | - name: Validate version format 37 | run: | 38 | VERSION="${{ steps.extract.outputs.version_without_v }}" 39 | # 验证版本号格式 (支持 SemVer: X.Y.Z 或 X.Y.Z-prerelease) 40 | if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then 41 | echo "错误:版本号格式不正确。应该为 X.Y.Z 或 X.Y.Z-prerelease 格式" 42 | echo "Error: Invalid version format. Should be X.Y.Z or X.Y.Z-prerelease" 43 | exit 1 44 | fi 45 | echo "✓ 版本号格式验证通过 / Version format validated" 46 | 47 | - name: Extract version from csproj 48 | id: csproj 49 | run: | 50 | CSPROJ_VERSION=$(grep -oP '(?<=)[^<]+' Chsword.Excel2Object/Chsword.Excel2Object.csproj) 51 | echo "csproj_version=$CSPROJ_VERSION" >> $GITHUB_OUTPUT 52 | echo "Project version: $CSPROJ_VERSION" 53 | 54 | - name: Compare versions 55 | run: | 56 | TAG_VERSION="${{ steps.extract.outputs.version_without_v }}" 57 | CSPROJ_VERSION="${{ steps.csproj.outputs.csproj_version }}" 58 | 59 | if [ "$TAG_VERSION" != "$CSPROJ_VERSION" ]; then 60 | echo "⚠️ 警告:Tag 版本 ($TAG_VERSION) 与 csproj 版本 ($CSPROJ_VERSION) 不一致" 61 | echo "⚠️ Warning: Tag version ($TAG_VERSION) does not match csproj version ($CSPROJ_VERSION)" 62 | echo "" 63 | echo "建议:请确保 Tag 版本与项目文件中的版本号一致" 64 | echo "Recommendation: Please ensure tag version matches the version in project file" 65 | exit 1 66 | fi 67 | echo "✓ 版本号一致性验证通过 / Version consistency validated" 68 | 69 | # 构建和测试 70 | build-and-test: 71 | name: Build and Test 72 | needs: validate-version 73 | runs-on: ${{ matrix.os }} 74 | strategy: 75 | matrix: 76 | os: [ubuntu-latest, windows-latest] 77 | fail-fast: false 78 | 79 | steps: 80 | - name: Checkout code 81 | uses: actions/checkout@v4 82 | 83 | - name: Setup .NET SDK 84 | uses: actions/setup-dotnet@v4 85 | with: 86 | dotnet-version: | 87 | 6.0.x 88 | 8.0.x 89 | 9.0.x 90 | 91 | - name: Restore dependencies 92 | run: dotnet restore 93 | 94 | - name: Build 95 | run: dotnet build --configuration Release --no-restore 96 | 97 | - name: Test 98 | run: dotnet test --configuration Release --no-build --verbosity normal --logger "trx;LogFileName=test-results.trx" 99 | 100 | - name: Upload test results 101 | if: always() 102 | uses: actions/upload-artifact@v4 103 | with: 104 | name: test-results-${{ matrix.os }} 105 | path: "**/test-results.trx" 106 | 107 | # 打包 NuGet 包 108 | pack-nuget: 109 | name: Pack NuGet Package 110 | needs: [validate-version, build-and-test] 111 | runs-on: ubuntu-latest 112 | 113 | steps: 114 | - name: Checkout code 115 | uses: actions/checkout@v4 116 | 117 | - name: Setup .NET SDK 118 | uses: actions/setup-dotnet@v4 119 | with: 120 | dotnet-version: | 121 | 6.0.x 122 | 8.0.x 123 | 9.0.x 124 | 125 | - name: Restore dependencies 126 | run: dotnet restore 127 | 128 | - name: Build 129 | run: dotnet build --configuration Release --no-restore 130 | 131 | - name: Pack NuGet package 132 | run: dotnet pack --configuration Release --no-build --output ./artifacts 133 | 134 | - name: Upload NuGet package artifact 135 | uses: actions/upload-artifact@v4 136 | with: 137 | name: nuget-package 138 | path: ./artifacts/*.nupkg 139 | retention-days: 90 140 | 141 | # 发布到 NuGet.org 142 | publish-nuget: 143 | name: Publish to NuGet 144 | needs: pack-nuget 145 | runs-on: ubuntu-latest 146 | environment: 147 | name: nuget-production 148 | url: https://www.nuget.org/packages/Chsword.Excel2Object/ 149 | 150 | steps: 151 | - name: Download NuGet package 152 | uses: actions/download-artifact@v4 153 | with: 154 | name: nuget-package 155 | path: ./artifacts 156 | 157 | - name: Setup .NET SDK 158 | uses: actions/setup-dotnet@v4 159 | with: 160 | dotnet-version: 9.0.x 161 | 162 | - name: Publish to NuGet.org 163 | run: | 164 | dotnet nuget push ./artifacts/*.nupkg \ 165 | --api-key ${{ secrets.NUGET_API_KEY }} \ 166 | --source https://api.nuget.org/v3/index.json \ 167 | --skip-duplicate 168 | env: 169 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 170 | 171 | # 创建 GitHub Release 172 | create-release: 173 | name: Create GitHub Release 174 | needs: [validate-version, build-and-test] 175 | runs-on: ubuntu-latest 176 | permissions: 177 | contents: write 178 | 179 | steps: 180 | - name: Checkout code 181 | uses: actions/checkout@v4 182 | with: 183 | fetch-depth: 0 184 | 185 | - name: Download NuGet package 186 | uses: actions/download-artifact@v4 187 | with: 188 | name: nuget-package 189 | path: ./artifacts 190 | 191 | - name: Extract release notes 192 | id: release_notes 193 | run: | 194 | VERSION="${{ needs.validate-version.outputs.version_without_v }}" 195 | 196 | # 从 README.md 提取当前版本的发布说明 197 | # Extract release notes for current version from README.md 198 | if grep -q "v$VERSION" README.md; then 199 | # 提取版本号对应的部分 200 | NOTES=$(awk "/\*\*.*v$VERSION/,/^\* \*\*[0-9]/" README.md | head -n -1) 201 | 202 | # 保存到文件 203 | echo "$NOTES" > release_notes.md 204 | echo "找到版本 $VERSION 的发布说明" 205 | echo "Found release notes for version $VERSION" 206 | else 207 | # 如果没有找到,使用默认说明 208 | echo "## Release $VERSION" > release_notes.md 209 | echo "" >> release_notes.md 210 | echo "详细的变更说明请查看 [README.md](https://github.com/chsword/Excel2Object/blob/main/README.md)。" >> release_notes.md 211 | echo "" >> release_notes.md 212 | echo "For detailed changes, please see [README.md](https://github.com/chsword/Excel2Object/blob/main/README.md)." >> release_notes.md 213 | echo "未找到版本 $VERSION 的发布说明,使用默认说明" 214 | echo "Release notes not found for version $VERSION, using default" 215 | fi 216 | 217 | cat release_notes.md 218 | 219 | - name: Determine if pre-release 220 | id: prerelease 221 | run: | 222 | VERSION="${{ needs.validate-version.outputs.version_without_v }}" 223 | # 检查版本号是否包含预发布标识符(如 alpha, beta, rc) 224 | if [[ $VERSION =~ - ]]; then 225 | echo "is_prerelease=true" >> $GITHUB_OUTPUT 226 | echo "这是一个预发布版本 / This is a pre-release" 227 | else 228 | echo "is_prerelease=false" >> $GITHUB_OUTPUT 229 | echo "这是一个正式版本 / This is a stable release" 230 | fi 231 | 232 | - name: Create GitHub Release 233 | uses: softprops/action-gh-release@v1 234 | with: 235 | name: Release ${{ needs.validate-version.outputs.version }} 236 | body_path: release_notes.md 237 | draft: false 238 | prerelease: ${{ steps.prerelease.outputs.is_prerelease }} 239 | files: | 240 | ./artifacts/*.nupkg 241 | token: ${{ secrets.GITHUB_TOKEN }} 242 | 243 | # 发布成功通知 244 | notify-success: 245 | name: Notify Success 246 | needs: [validate-version, publish-nuget, create-release] 247 | runs-on: ubuntu-latest 248 | if: success() 249 | 250 | steps: 251 | - name: Display success message 252 | run: | 253 | echo "🎉 发布成功!/ Release successful!" 254 | echo "" 255 | echo "版本 / Version: ${{ needs.validate-version.outputs.version }}" 256 | echo "" 257 | echo "📦 NuGet: https://www.nuget.org/packages/Chsword.Excel2Object/${{ needs.validate-version.outputs.version_without_v }}" 258 | echo "🏷️ GitHub Release: https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate-version.outputs.version }}" 259 | echo "" 260 | echo "✓ NuGet 包已发布" 261 | echo "✓ GitHub Release 已创建" 262 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Excel2Object 2 | 3 | [![install from nuget](http://img.shields.io/nuget/v/Chsword.Excel2Object.svg?style=flat-square)](https://www.nuget.org/packages/Chsword.Excel2Object) 4 | [![release](https://img.shields.io/github/release/chsword/Excel2Object.svg?style=flat-square)](https://github.com/chsword/Excel2Object/releases) 5 | [![.NET CI](https://github.com/chsword/Excel2Object/actions/workflows/dotnet-ci.yml/badge.svg)](https://github.com/chsword/Excel2Object/actions/workflows/dotnet-ci.yml) 6 | [![CodeFactor](https://www.codefactor.io/repository/github/chsword/excel2object/badge)](https://www.codefactor.io/repository/github/chsword/excel2object) 7 | 8 | Excel convert to .NET Object / .NET Object convert to Excel. 9 | 10 | - [Top](#excel2object) 11 | - [NuGet install](#nuget-install) 12 | - [Release notes and roadmap](#release-notes-and-roadmap) 13 | - [Demo code](#demo-code) 14 | - [Document](#document) 15 | - [Development and Release](#development-and-release) 16 | - [Contributors](#contributors) 17 | - [Reference](#reference) 18 | 19 | ## Platform Support 20 | 21 | [![.NET 4.7.2 +](https://img.shields.io/badge/-4.7.2%2B-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 22 | [![.NET Standard 2.0](https://img.shields.io/badge/-standard2.0-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 23 | [![.NET Standard 2.1](https://img.shields.io/badge/-standard2.1-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 24 | [![.NET 6.0](https://img.shields.io/badge/-6.0-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 25 | [![.NET 8.0](https://img.shields.io/badge/-8.0-brightgreen?logo=dotnet&style=for-the-badge&color=blue)](#) 26 | 27 | ## NuGet Install 28 | 29 | ``` powershell 30 | PM> Install-Package Chsword.Excel2Object 31 | ``` 32 | 33 | Or using .NET CLI: 34 | ``` bash 35 | dotnet add package Chsword.Excel2Object 36 | ``` 37 | 38 | ## Release Notes and Roadmap 39 | 40 | ### Features Not Yet Supported 41 | 42 | - [ ] CLI tool 43 | - [x] Support auto width column ✅ **New in v2.0.1** 44 | - [x] Support date/datetime/time formats in Excel ✅ **New in v2.0.2** - See [DateTimeFormats.md](DateTimeFormats.md) 45 | 46 | ### Release Notes 47 | 48 | * **2025.10.11** - v2.0.2 49 | - [x] ✨ **NEW:** Comprehensive date/time format support (56 formats) - See [DateTimeFormats.md](DateTimeFormats.md) 50 | - ISO 8601 formats with/without timezone and milliseconds 51 | - Multiple date separators (dash, slash, dot) 52 | - 12-hour and 24-hour time formats 53 | - Time-only formats 54 | - Regional format support (US, European, etc.) 55 | - Backward compatible with existing Chinese date formats (年月日) 56 | - [x] ✨ **Updated:** NPOI to 2.7.5 57 | - [x] ✨ **Updated:** SixLabors.ImageSharp to 3.1.11 (Fixed security vulnerability) 58 | 59 | * **2025.07.23** - v2.0.1 60 | - [x] ✨ **NEW:** Auto column width adjustment based on content 61 | - Automatically calculates optimal column widths 62 | - Supports minimum and maximum width constraints 63 | - Handles Chinese/Unicode characters properly 64 | - Configurable through `ExcelExporterOptions` 65 | 66 | * **2024.10.21** 67 | - [x] Updated SixLabors.ImageSharp to 2.1.9 68 | - [x] Tested for .NET 8.0 69 | 70 | * **2024.05.10** 71 | - [x] Support .NET 8.0 / .NET 6.0 / .NET Standard 2.1 / .NET Standard 2.0 / .NET Framework 4.7.2 72 | - [x] Cleared deprecated libraries 73 | 74 | * **2023.11.02** 75 | - [x] Support column title mapping - [Issue39DynamicMappingTitle.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Issue39DynamicMappingTitle.cs) 76 | 77 | * **2023.07.31** 78 | - [x] Support DateTime and Nullable format, such as `[ExcelColumn("Title",Format="yyyy-MM-dd HH:mm:ss")]` 79 | 80 | * **2023.03.26** 81 | - [x] Support special symbols in column titles #37 - [Issue37SpecialCharTest.cs](https://github.com/chsword/Excel2Object/commit/273122275e724367bb6154e03df61702fcec81b3#diff-5f0f5f7558bf7d4207cfa752a4506c4df89d9b491e2501e4862aff0c2276bd61) 82 | 83 | * **2023.02.20** 84 | - [x] Support platforms: .NET Standard 2.0/2.1, .NET 6.0, .NET Framework 4.7.2 85 | 86 | * **2022.03.19** 87 | - [x] Support ExcelImporterOptions, Skipline - [Issue32SkipLineImport.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Issue32SkipLineImport.cs) 88 | - [x] Fixed superclass property bug - [Issue31SuperClass.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Issue31SuperClass.cs) 89 | 90 | * **2021.11.4** 91 | - [x] Multiple sheet support - [Pr28MultipleSheetTest.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Pr28MultipleSheetTest.cs) 92 | 93 | * **2021.10.23** 94 | - [x] Fixed Nullable DateTime bug @SunBrook 95 | 96 | * **2021.10.22** 97 | - [x] Support Nullable types - [Pr24NullableTest.cs](https://github.com/chsword/Excel2Object/blob/main/Chsword.Excel2Object.Tests/Pr24NullableTest.cs) @SunBrook 98 | 99 | * **2021.5.28** 100 | - [x] Support styling for headers & cells, new [ExcelColumnAttribute] for columns 101 | - [x] Support Functions - [ExcelFunctions.md](./ExcelFunctions.md) 102 | 103 | ```C# 104 | var list = new List 105 | { 106 | new Pr20Model 107 | { 108 | Fullname = "AAA", Mobile = "123456798123" 109 | }, 110 | new Pr20Model 111 | { 112 | Fullname = "BBB", Mobile = "234" 113 | } 114 | }; 115 | var bytes = ExcelHelper.ObjectToExcelBytes(list, ExcelType.Xlsx); 116 | 117 | // Model definition 118 | [ExcelTitle("SheetX")] 119 | public class Pr20Model 120 | { 121 | [ExcelColumn("Full name", CellFontColor = ExcelStyleColor.Red)] 122 | public string Fullname { get; set; } 123 | 124 | [ExcelColumn("Phone Number", 125 | HeaderFontFamily = "Normal", 126 | HeaderBold = true, 127 | HeaderFontHeight = 30, 128 | HeaderItalic = true, 129 | HeaderFontColor = ExcelStyleColor.Blue, 130 | HeaderUnderline = true, 131 | HeaderAlignment = HorizontalAlignment.Right, 132 | //cell 133 | CellAlignment = HorizontalAlignment.Justify 134 | )] 135 | public string Mobile { get; set; } 136 | } 137 | ``` 138 | 139 | * **v2.0.0.113** 140 | ``` 141 | Converted project to .NET Standard 2.0 and .NET Framework 4.5.2 142 | Fixed bugs #12 #13 143 | ``` 144 | 145 | * **v1.0.0.80** 146 | - [x] Support simple formulas 147 | - [x] Support standard Excel model 148 | - [x] Excel & JSON conversion 149 | - [x] Excel & Dictionary conversion 150 | 151 | ``` 152 | Support Uri to hyperlink cell 153 | Also support text cell to Uri type 154 | ``` 155 | 156 | * **v1.0.0.43** 157 | ``` 158 | Support xlsx format [thanks Soar360] 159 | Support complex Boolean type 160 | ``` 161 | 162 | * **v1.0.0.36** 163 | ``` 164 | Add ExcelToObject(bytes) 165 | ``` 166 | 167 | ## Demo Code 168 | 169 | ### Define Model 170 | 171 | ``` csharp 172 | public class ReportModel 173 | { 174 | [Excel("My Title", Order=1)] 175 | public string Title { get; set; } 176 | 177 | [Excel("User Name", Order=2)] 178 | public string Name { get; set; } 179 | } 180 | ``` 181 | 182 | ### Create Model List 183 | 184 | ``` csharp 185 | var models = new List 186 | { 187 | new ReportModel{Name="a", Title="b"}, 188 | new ReportModel{Name="c", Title="d"}, 189 | new ReportModel{Name="f", Title="e"} 190 | }; 191 | ``` 192 | 193 | ### Convert Object to Excel File 194 | 195 | ``` csharp 196 | var exporter = new ExcelExporter(); 197 | var bytes = exporter.ObjectToExcelBytes(models); 198 | File.WriteAllBytes("C:\\demo.xls", bytes); 199 | ``` 200 | 201 | ### Convert Excel File to Object 202 | 203 | ``` csharp 204 | var importer = new ExcelImporter(); 205 | IEnumerable result = importer.ExcelToObject("c:\\demo.xls"); 206 | 207 | // You can also use bytes directly 208 | // IEnumerable result = importer.ExcelToObject(bytes); 209 | ``` 210 | 211 | ### Auto Column Width (New Feature) 212 | 213 | ``` csharp 214 | // Enable auto column width adjustment 215 | var bytes = ExcelHelper.ObjectToExcelBytes(models, options => 216 | { 217 | options.ExcelType = ExcelType.Xlsx; 218 | options.AutoColumnWidth = true; // Enable auto width 219 | options.MinColumnWidth = 8; // Minimum width in characters 220 | options.MaxColumnWidth = 50; // Maximum width in characters 221 | options.DefaultColumnWidth = 16; // Default width when auto is disabled 222 | }); 223 | ``` 224 | 225 | ### Use with ASP.NET MVC 226 | 227 | In ASP.NET MVC models, the `DisplayAttribute` can be supported like `ExcelTitleAttribute`. 228 | 229 | ## Document 230 | 231 | For more information, please visit: http://www.cnblogs.com/chsword/p/excel2object.html 232 | 233 | ## Development and Release 234 | 235 | - **[Automated Release Script `release.ps1`](release.ps1)** - PowerShell script for one-click version update, commit, and tag creation 236 | - **[Automated Release System Documentation](RELEASE_AUTOMATION.md)** - Complete guide including Copilot instructions, version management, and automated release workflow 237 | - **[Versioning Guidelines](.github/VERSIONING.md)** - Semantic versioning specification and version increment rules 238 | - **[Release Process Guide](.github/RELEASE_GUIDE.md)** - Detailed release steps and troubleshooting guide 239 | - **[Copilot Instructions](.github/copilot-instructions.md)** - GitHub Copilot development guidelines and project conventions 240 | 241 | ### Quick Release 242 | 243 | Use the automation script for one-click release: 244 | 245 | ```powershell 246 | # Windows 247 | .\release.ps1 -Version 2.0.4 248 | 249 | # Linux/macOS (PowerShell Core required) 250 | pwsh ./release.ps1 -Version 2.0.4 251 | ``` 252 | 253 | See [RELEASE_AUTOMATION.md](RELEASE_AUTOMATION.md) for detailed instructions. 254 | 255 | ## Contributors 256 | 257 | [![Contributors](https://contrib.rocks/image?repo=chsword/Excel2Object)](https://github.com/chsword/Excel2Object/graphs/contributors) 258 | 259 | ## Reference 260 | 261 | - https://github.com/tonyqus/npoi 262 | - https://github.com/chsword/ctrc 263 | 264 | ## License 265 | 266 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 267 | -------------------------------------------------------------------------------- /Chsword.Excel2Object.Tests/DateTimeFormatTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using Chsword.Excel2Object.Tests.Models; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | 9 | namespace Chsword.Excel2Object.Tests; 10 | 11 | [TestClass] 12 | public class DateTimeFormatTest : BaseExcelTest 13 | { 14 | [TestMethod] 15 | public void TestISO8601DateTimeFormat() 16 | { 17 | // Test ISO 8601 format: yyyy-MM-ddTHH:mm:ss 18 | var testDate = new DateTime(2023, 7, 31, 14, 30, 45); 19 | var models = new List 20 | { 21 | new() 22 | { 23 | Name = "Test Person", 24 | Age = 25, 25 | Birthday = testDate 26 | } 27 | }; 28 | 29 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 30 | var importer = new ExcelImporter(); 31 | var result = importer.ExcelToObject(bytes).ToList(); 32 | 33 | Assert.AreEqual(1, result.Count); 34 | Assert.IsNotNull(result[0].Birthday); 35 | Assert.AreEqual(testDate.Date, result[0].Birthday!.Value.Date); 36 | } 37 | 38 | [TestMethod] 39 | public void TestCommonDateFormats() 40 | { 41 | var testDate = new DateTime(2023, 12, 25); 42 | var models = new List 43 | { 44 | new() 45 | { 46 | Name = "Test Person", 47 | Age = 30, 48 | Birthday = testDate 49 | } 50 | }; 51 | 52 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 53 | var importer = new ExcelImporter(); 54 | var result = importer.ExcelToObject(bytes).ToList(); 55 | 56 | Assert.AreEqual(1, result.Count); 57 | Assert.IsNotNull(result[0].Birthday); 58 | Assert.AreEqual(testDate.Date, result[0].Birthday!.Value.Date); 59 | } 60 | 61 | [TestMethod] 62 | public void TestDateTimeWithSeconds() 63 | { 64 | var testDate = new DateTime(2023, 6, 15, 9, 45, 30); 65 | var models = new List 66 | { 67 | new() 68 | { 69 | Name = "Test Person", 70 | Age = 35, 71 | Birthday = testDate, 72 | CreateTime = testDate 73 | } 74 | }; 75 | 76 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 77 | var importer = new ExcelImporter(); 78 | var result = importer.ExcelToObject(bytes).ToList(); 79 | 80 | Assert.AreEqual(1, result.Count); 81 | Assert.IsNotNull(result[0].Birthday); 82 | Assert.AreEqual(testDate.Date, result[0].Birthday!.Value.Date); 83 | } 84 | 85 | [TestMethod] 86 | public void TestNullableDateTimeFormat() 87 | { 88 | var testDate = new DateTime(2023, 3, 10, 18, 20, 0); 89 | var models = new List 90 | { 91 | new() 92 | { 93 | Name = "Test Person 1", 94 | Age = 28, 95 | Birthday = testDate 96 | }, 97 | new() 98 | { 99 | Name = "Test Person 2", 100 | Age = 32, 101 | Birthday = null 102 | } 103 | }; 104 | 105 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 106 | var importer = new ExcelImporter(); 107 | var result = importer.ExcelToObject(bytes).ToList(); 108 | 109 | Assert.AreEqual(2, result.Count); 110 | Assert.IsNotNull(result[0].Birthday); 111 | Assert.AreEqual(testDate.Date, result[0].Birthday!.Value.Date); 112 | Assert.IsNull(result[1].Birthday); 113 | } 114 | 115 | [TestMethod] 116 | public void TestDateTimeWithCustomFormat() 117 | { 118 | var testDate = new DateTime(2023, 11, 5, 14, 30, 0); 119 | var models = new List 120 | { 121 | new() 122 | { 123 | Name = "Test Person", 124 | Age = 40, 125 | Birthday = testDate, 126 | Birthday2 = testDate 127 | } 128 | }; 129 | 130 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 131 | var importer = new ExcelImporter(); 132 | var result = importer.ExcelToObject(bytes).ToList(); 133 | 134 | Assert.AreEqual(1, result.Count); 135 | Assert.AreEqual(testDate.Date, result[0].Birthday.Date); 136 | Assert.IsNotNull(result[0].Birthday2); 137 | Assert.AreEqual(testDate.Date, result[0].Birthday2!.Value.Date); 138 | } 139 | 140 | [TestMethod] 141 | public void TestMultipleDateFormatsInSameFile() 142 | { 143 | var testDate1 = new DateTime(2023, 1, 15); 144 | var testDate2 = new DateTime(2023, 8, 20, 16, 45, 0); 145 | var testDate3 = new DateTime(2023, 12, 31, 23, 59, 59); 146 | 147 | var models = new List 148 | { 149 | new() 150 | { 151 | Name = "Person 1", 152 | Age = 25, 153 | Birthday = testDate1, 154 | CreateTime = testDate1 155 | }, 156 | new() 157 | { 158 | Name = "Person 2", 159 | Age = 30, 160 | Birthday = testDate2, 161 | CreateTime = testDate2 162 | }, 163 | new() 164 | { 165 | Name = "Person 3", 166 | Age = 35, 167 | Birthday = testDate3, 168 | CreateTime = testDate3 169 | } 170 | }; 171 | 172 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 173 | var importer = new ExcelImporter(); 174 | var result = importer.ExcelToObject(bytes).ToList(); 175 | 176 | Assert.AreEqual(3, result.Count); 177 | Assert.AreEqual(testDate1.Date, result[0].Birthday!.Value.Date); 178 | Assert.AreEqual(testDate2.Date, result[1].Birthday!.Value.Date); 179 | Assert.AreEqual(testDate3.Date, result[2].Birthday!.Value.Date); 180 | } 181 | 182 | [TestMethod] 183 | public void TestDateOnlyFormat() 184 | { 185 | // Test date without time component 186 | var testDate = new DateTime(2023, 5, 20); 187 | var models = new List 188 | { 189 | new() 190 | { 191 | Name = "Test Person", 192 | Age = 27, 193 | Birthday = testDate 194 | } 195 | }; 196 | 197 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 198 | var importer = new ExcelImporter(); 199 | var result = importer.ExcelToObject(bytes).ToList(); 200 | 201 | Assert.AreEqual(1, result.Count); 202 | Assert.IsNotNull(result[0].Birthday); 203 | Assert.AreEqual(testDate.Date, result[0].Birthday!.Value.Date); 204 | } 205 | 206 | [TestMethod] 207 | public void TestMinMaxDateValues() 208 | { 209 | // Test edge cases with min and max reasonable date values 210 | var minDate = new DateTime(1900, 1, 1); 211 | var maxDate = new DateTime(2099, 12, 31); 212 | 213 | var models = new List 214 | { 215 | new() 216 | { 217 | Name = "Min Date Person", 218 | Age = 25, 219 | Birthday = minDate 220 | }, 221 | new() 222 | { 223 | Name = "Max Date Person", 224 | Age = 30, 225 | Birthday = maxDate 226 | } 227 | }; 228 | 229 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 230 | var importer = new ExcelImporter(); 231 | var result = importer.ExcelToObject(bytes).ToList(); 232 | 233 | Assert.AreEqual(2, result.Count); 234 | Assert.AreEqual(minDate.Date, result[0].Birthday!.Value.Date); 235 | Assert.AreEqual(maxDate.Date, result[1].Birthday!.Value.Date); 236 | } 237 | 238 | [TestMethod] 239 | public void TestLeapYearDate() 240 | { 241 | // Test leap year date: February 29, 2024 242 | var leapDate = new DateTime(2024, 2, 29); 243 | var models = new List 244 | { 245 | new() 246 | { 247 | Name = "Leap Year Person", 248 | Age = 29, 249 | Birthday = leapDate 250 | } 251 | }; 252 | 253 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 254 | var importer = new ExcelImporter(); 255 | var result = importer.ExcelToObject(bytes).ToList(); 256 | 257 | Assert.AreEqual(1, result.Count); 258 | Assert.AreEqual(leapDate.Date, result[0].Birthday!.Value.Date); 259 | } 260 | 261 | [TestMethod] 262 | public void TestMidnightAndNoonTimes() 263 | { 264 | // Test midnight and noon times 265 | var midnight = new DateTime(2023, 7, 15, 0, 0, 0); 266 | var noon = new DateTime(2023, 7, 15, 12, 0, 0); 267 | 268 | var models = new List 269 | { 270 | new() 271 | { 272 | Name = "Midnight Person", 273 | Age = 25, 274 | Birthday = midnight, 275 | CreateTime = midnight 276 | }, 277 | new() 278 | { 279 | Name = "Noon Person", 280 | Age = 30, 281 | Birthday = noon, 282 | CreateTime = noon 283 | } 284 | }; 285 | 286 | var bytes = ExcelHelper.ObjectToExcelBytes(models); 287 | var importer = new ExcelImporter(); 288 | var result = importer.ExcelToObject(bytes).ToList(); 289 | 290 | Assert.AreEqual(2, result.Count); 291 | Assert.AreEqual(midnight.Date, result[0].Birthday!.Value.Date); 292 | Assert.AreEqual(noon.Date, result[1].Birthday!.Value.Date); 293 | } 294 | } 295 | --------------------------------------------------------------------------------