using System.Text.Json; using System.Text.RegularExpressions; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; public static class ExcelGenerator { private const string CodeOutPath = "src/config/"; private const string JsonOutPath = "resource/config/"; #if DEBUG private const string ExcelFilePath = "excelFile"; #else private const string ExcelFilePath = "excel/excelFile"; #endif private class MappingData { public string TypeStr; public string TypeName; public MappingData(string typeStr, string typeName) { TypeStr = typeStr; TypeName = typeName; } } private class ExcelData { public string TableName; public string OutCode; public List<string> ColumnNames = new List<string>(); public Dictionary<string, MappingData> ColumnMappingData = new Dictionary<string, MappingData>(); public Dictionary<string, Type> ColumnType = new Dictionary<string, Type>(); public List<Dictionary<string, object>> DataList = new List<Dictionary<string, object>>(); } public static bool ExportExcel() { Console.WriteLine("当前路径: " + Environment.CurrentDirectory); try { var excelDataList = new List<ExcelData>(); var directoryInfo = new DirectoryInfo(ExcelFilePath); if (directoryInfo.Exists) { var fileInfos = directoryInfo.GetFiles(); foreach (var fileInfo in fileInfos) { if (fileInfo.Extension == ".xlsx") { if (fileInfo.Name == "ExcelConfig.xlsx") { throw new Exception("excel表文件名称不允许叫'ExcelConfig.xlsx'!"); } Console.WriteLine("excel表: " + fileInfo.FullName); excelDataList.Add(ReadExcel(fileInfo.FullName)); } } } Console.WriteLine($"一共检测到excel表共{excelDataList.Count}张."); if (Directory.Exists(CodeOutPath)) { Directory.Delete(CodeOutPath, true); } if (Directory.Exists(JsonOutPath)) { Directory.Delete(JsonOutPath, true); } Directory.CreateDirectory(CodeOutPath); Directory.CreateDirectory(JsonOutPath); //保存配置和代码 foreach (var excelData in excelDataList) { File.WriteAllText(CodeOutPath + "ExcelConfig_" + excelData.TableName + ".cs", excelData.OutCode); var config = new JsonSerializerOptions(); config.WriteIndented = true; File.WriteAllText(JsonOutPath + excelData.TableName + ".json", JsonSerializer.Serialize(excelData.DataList, config)); } //生成加载代码 var code = GeneratorInitCode(excelDataList); File.WriteAllText(CodeOutPath + "ExcelConfig.cs", code); } catch (Exception e) { PrintError(e.ToString()); return false; } return true; } private static string GeneratorInitCode(List<ExcelData> excelList) { var code = $"using System;\n"; code += $"using System.Collections.Generic;\n"; code += $"using System.Text.Json;\n"; code += $"using Godot;\n"; code += $"\n"; code += $"namespace Config;\n"; code += $"\n"; code += $"public static partial class ExcelConfig\n"; code += $"{{\n"; var fieldCode = ""; var callFuncCode = ""; var funcCode = ""; foreach (var excelData in excelList) { var idName = excelData.ColumnNames[0]; var idTypeStr = excelData.ColumnMappingData[idName].TypeStr; fieldCode += $" /// <summary>\n"; fieldCode += $" /// {excelData.TableName}.xlsx表数据集合, 以 List 形式存储, 数据顺序与 Excel 表相同\n"; fieldCode += $" /// </summary>\n"; fieldCode += $" public static List<{excelData.TableName}> {excelData.TableName}_List {{ get; private set; }}\n"; fieldCode += $" /// <summary>\n"; fieldCode += $" /// {excelData.TableName}.xlsx表数据集合, 里 Map 形式存储, key 为 {idName}\n"; fieldCode += $" /// </summary>\n"; fieldCode += $" public static Dictionary<{idTypeStr}, {excelData.TableName}> {excelData.TableName}_Map {{ get; private set; }}\n"; fieldCode += $"\n"; callFuncCode += $" _Init{excelData.TableName}Config();\n"; funcCode += $" private static void _Init{excelData.TableName}Config()\n"; funcCode += $" {{\n"; funcCode += $" try\n"; funcCode += $" {{\n"; funcCode += $" var text = _ReadConfigAsText(\"res://resource/config/{excelData.TableName}.json\");\n"; funcCode += $" {excelData.TableName}_List = JsonSerializer.Deserialize<List<{excelData.TableName}>>(text);\n"; funcCode += $" {excelData.TableName}_Map = new Dictionary<{idTypeStr}, {excelData.TableName}>();\n"; funcCode += $" foreach (var item in {excelData.TableName}_List)\n"; funcCode += $" {{\n"; funcCode += $" {excelData.TableName}_Map.Add(item.{idName}, item);\n"; funcCode += $" }}\n"; funcCode += $" }}\n"; funcCode += $" catch (Exception e)\n"; funcCode += $" {{\n"; funcCode += $" GD.PrintErr(e.ToString());\n"; funcCode += $" throw new Exception(\"初始化表'{excelData.TableName}'失败!\");\n"; funcCode += $" }}\n"; funcCode += $" }}\n"; } code += fieldCode; code += $"\n"; code += $" private static bool _init = false;\n"; code += $" /// <summary>\n"; code += $" /// 初始化所有配置表数据\n"; code += $" /// </summary>\n"; code += $" public static void Init()\n"; code += $" {{\n"; code += $" if (_init) return;\n"; code += $" _init = true;\n"; code += $"\n"; code += callFuncCode; code += $" }}\n"; code += funcCode; code += $" private static string _ReadConfigAsText(string path)\n"; code += $" {{\n"; code += $" var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);\n"; code += $" var asText = file.GetAsText();\n"; code += $" file.Dispose();\n"; code += $" return asText;\n"; code += $" }}\n"; code += $"}}"; return code; } private static ExcelData ReadExcel(string excelPath) { var excelData = new ExcelData(); //文件名称 var fileName = Path.GetFileNameWithoutExtension(excelPath).FirstToUpper(); excelData.TableName = fileName; //输出代码 var outStr = $"using System.Text.Json.Serialization;\n"; outStr += $"using System.Collections.Generic;\n\n"; outStr += $"namespace Config;\n\n"; outStr += $"public static partial class ExcelConfig\n{{\n"; outStr += $" public class {fileName}\n"; outStr += $" {{\n"; var sourceFile = excelPath; //行数 var rowCount = -1; //列数 var columnCount = -1; //加载表数据 var workbook = new XSSFWorkbook(sourceFile); using (workbook) { var sheet1 = workbook.GetSheet("Sheet1"); rowCount = sheet1.LastRowNum; //先解析表中的列名, 注释, 类型 var names = sheet1.GetRow(0); var descriptions = sheet1.GetRow(1); var types = sheet1.GetRow(2); columnCount = names.LastCellNum; foreach (var cell in names) { //字段名称 var field = GetCellStringValue(cell); if (string.IsNullOrEmpty(field)) { if (cell.ColumnIndex == 0) { throw new Exception($"表'{fileName}'的列数为0!"); } //到达最后一列了 columnCount = cell.ColumnIndex; break; } field = field.FirstToUpper(); excelData.ColumnNames.Add(field); var descriptionCell = descriptions.GetCell(cell.ColumnIndex); //描述 string description; if (descriptionCell != null) { description = GetCellStringValue(descriptionCell).Replace("\n", " <br/>\n /// "); } else { description = ""; } //类型 var typeString = GetCellStringValue(types.GetCell(cell.ColumnIndex)); if (string.IsNullOrEmpty(typeString)) { throw new Exception($"表'{fileName}'中'{field}'这一列类型为空!"); } //尝试解析类型 MappingData mappingData; try { mappingData = ConvertToType(typeString.Replace(" ", "")); } catch (Exception e) { PrintError(e.ToString()); throw new Exception($"表'{fileName}'中'{field}'这一列类型描述语法错误: {typeString}"); } if (!excelData.ColumnMappingData.TryAdd(field, mappingData)) { throw new Exception($"表'{fileName}'中存在相同名称的列: '{field}'!"); } outStr += $" /// <summary>\n"; outStr += $" /// {description}\n"; outStr += $" /// </summary>\n"; outStr += $" [JsonInclude]\n"; outStr += $" public {mappingData.TypeStr} {field} {{ get; private set; }}\n\n"; } outStr += " }\n"; outStr += "}"; //解析字段类型 foreach (var kv in excelData.ColumnMappingData) { var typeName = kv.Value.TypeName; var type = Type.GetType(typeName); if (type == null) { throw new Exception($"表'{fileName}'中'{kv.Key}'这一列类型未知! " + kv.Value.TypeStr); } excelData.ColumnType.Add(kv.Key, type); } //解析数据 for (int i = 3; i <= rowCount; i++) { Dictionary<string, object> data = null; var row = sheet1.GetRow(i); for (int j = 0; j < columnCount; j++) { var cell = row.GetCell(j); var strValue = GetCellStringValue(cell); //如果这一行的第一列数据为空, 则跳过这一行 if (j == 0 && string.IsNullOrEmpty(strValue)) { break; } else if (data == null) { data = new Dictionary<string, object>(); excelData.DataList.Add(data); } var fieldName = excelData.ColumnNames[j]; var mappingData = excelData.ColumnMappingData[fieldName]; try { switch (mappingData.TypeStr) { case "bool": case "boolean": data.Add(fieldName, GetCellBooleanValue(cell)); break; case "byte": data.Add(fieldName, Convert.ToByte(GetCellNumberValue(cell))); break; case "sbyte": data.Add(fieldName, Convert.ToSByte(GetCellNumberValue(cell))); break; case "short": data.Add(fieldName, Convert.ToInt16(GetCellNumberValue(cell))); break; case "ushort": data.Add(fieldName, Convert.ToUInt16(GetCellNumberValue(cell))); break; case "int": data.Add(fieldName, Convert.ToInt32(GetCellNumberValue(cell))); break; case "uint": data.Add(fieldName, Convert.ToUInt32(GetCellNumberValue(cell))); break; case "long": data.Add(fieldName, Convert.ToInt64(GetCellNumberValue(cell))); break; case "ulong": data.Add(fieldName, Convert.ToUInt64(GetCellNumberValue(cell))); break; case "float": data.Add(fieldName, Convert.ToSingle(GetCellNumberValue(cell))); break; case "double": data.Add(fieldName, GetCellNumberValue(cell)); break; case "string": data.Add(fieldName, GetCellStringValue(cell)); break; default: { var cellStringValue = GetCellStringValue(cell); if (cellStringValue.Length == 0) { data.Add(fieldName, null); } else { data.Add(fieldName, JsonSerializer.Deserialize(cellStringValue, excelData.ColumnType[fieldName])); } } break; } } catch (Exception e) { PrintError(e.ToString()); throw new Exception($"解析表'{fileName}'第'{i + 1}'行第'{j + 1}'列数据时发生异常"); } } } } excelData.OutCode = outStr; return excelData; } private static string GetCellStringValue(ICell cell) { if (cell == null) { return ""; } switch (cell.CellType) { case CellType.Numeric: return cell.NumericCellValue.ToString(); case CellType.String: return cell.StringCellValue; case CellType.Formula: return cell.CellFormula; case CellType.Boolean: return cell.BooleanCellValue ? "true" : "false"; } return ""; } private static double GetCellNumberValue(ICell cell) { if (cell == null) { return 0; } return cell.NumericCellValue; } private static bool GetCellBooleanValue(ICell cell) { if (cell == null) { return false; } return cell.BooleanCellValue; } private static MappingData ConvertToType(string str) { if (Regex.IsMatch(str, "^\\w+$")) { var typeStr = TypeStrMapping(str); var typeName = TypeNameMapping(str); return new MappingData(typeStr, typeName); } else if (str.StartsWith('{')) { var tempStr = str.Substring(1, str.Length - 2); var index = tempStr.IndexOf(':'); if (index == -1) { throw new Exception("类型描述语法错误!"); } var keyStr = tempStr.Substring(0, index); if (!IsBaseType(keyStr)) { throw new Exception($"字典key类型必须是基础类型!"); } var type1 = ConvertToType(keyStr); var type2 = ConvertToType(tempStr.Substring(index + 1)); var typeStr = $"Dictionary<{type1.TypeStr}, {type2.TypeStr}>"; var typeName = $"System.Collections.Generic.Dictionary`2[[{type1.TypeName}],[{type2.TypeName}]]"; return new MappingData(typeStr, typeName); } else if (str.StartsWith('[')) { var tempStr = str.Substring(1, str.Length - 2); var typeData = ConvertToType(tempStr); var typeStr = typeData.TypeStr + "[]"; var typeName = typeData.TypeName + "[]"; return new MappingData(typeStr, typeName); } throw new Exception("类型描述语法错误!"); } private static string TypeStrMapping(string typeName) { switch (typeName) { case "boolean": return "bool"; case "vector2": return "SerializeVector2"; case "vector3": return "SerializeVector3"; case "color": return "SerializeColor"; } return typeName; } private static string TypeNameMapping(string typeName) { switch (typeName) { case "bool": case "boolean": return typeof(bool).FullName; case "byte": return typeof(byte).FullName; case "sbyte": return typeof(sbyte).FullName; case "short": return typeof(short).FullName; case "ushort": return typeof(ushort).FullName; case "int": return typeof(int).FullName; case "uint": return typeof(uint).FullName; case "long": return typeof(long).FullName; case "ulong": return typeof(ulong).FullName; case "string": return typeof(string).FullName; case "float": return typeof(float).FullName; case "double": return typeof(double).FullName; case "vector2": return "SerializeVector2"; case "vector3": return "SerializeVector3"; case "color": return "SerializeColor"; } return typeName; } private static bool IsBaseType(string typeName) { switch (typeName) { case "bool": case "boolean": case "byte": case "sbyte": case "short": case "ushort": case "int": case "uint": case "long": case "ulong": case "string": case "float": case "double": return true; } return false; } private static void PrintError(string message) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(message); Console.ResetColor(); } /// <summary> /// 字符串首字母小写 /// </summary> public static string FirstToLower(this string str) { return str.Substring(0, 1).ToLower() + str.Substring(1); } /// <summary> /// 字符串首字母大写 /// </summary> public static string FirstToUpper(this string str) { return str.Substring(0, 1).ToUpper() + str.Substring(1); } }