Newer
Older
DungeonShooting / DungeonShooting_ExcelTool / ExcelGenerator.cs
  1. using System.Text.Json;
  2. using System.Text.RegularExpressions;
  3. using NPOI.SS.UserModel;
  4. using NPOI.XSSF.UserModel;
  5.  
  6. public static class ExcelGenerator
  7. {
  8. private const string CodeOutPath = "src/config/";
  9. private const string JsonOutPath = "resource/config/";
  10. #if DEBUG
  11. private const string ExcelFilePath = "excelFile";
  12. #else
  13. private const string ExcelFilePath = "excel/excelFile";
  14. #endif
  15. private class MappingData
  16. {
  17. public string TypeStr;
  18. public string TypeName;
  19.  
  20. public MappingData(string typeStr, string typeName)
  21. {
  22. TypeStr = typeStr;
  23. TypeName = typeName;
  24. }
  25. }
  26.  
  27. private class ExcelData
  28. {
  29. public string TableName;
  30. public string OutCode;
  31. public List<string> ColumnNames = new List<string>();
  32. public Dictionary<string, MappingData> ColumnMappingData = new Dictionary<string, MappingData>();
  33. public Dictionary<string, Type> ColumnType = new Dictionary<string, Type>();
  34. public List<Dictionary<string, object>> DataList = new List<Dictionary<string, object>>();
  35. }
  36. public static bool ExportExcel()
  37. {
  38. Console.WriteLine("当前路径: " + Environment.CurrentDirectory);
  39. try
  40. {
  41. var excelDataList = new List<ExcelData>();
  42. var directoryInfo = new DirectoryInfo(ExcelFilePath);
  43. if (directoryInfo.Exists)
  44. {
  45. var fileInfos = directoryInfo.GetFiles();
  46. foreach (var fileInfo in fileInfos)
  47. {
  48. if (fileInfo.Extension == ".xlsx")
  49. {
  50. if (fileInfo.Name == "ExcelConfig.xlsx")
  51. {
  52. throw new Exception("excel表文件名称不允许叫'ExcelConfig.xlsx'!");
  53. }
  54. Console.WriteLine("excel表: " + fileInfo.FullName);
  55. excelDataList.Add(ReadExcel(fileInfo.FullName));
  56. }
  57. }
  58. }
  59.  
  60. Console.WriteLine($"一共检测到excel表共{excelDataList.Count}张.");
  61. if (excelDataList.Count == 0)
  62. {
  63. return true;
  64. }
  65. if (Directory.Exists(CodeOutPath))
  66. {
  67. Directory.Delete(CodeOutPath, true);
  68. }
  69. if (Directory.Exists(JsonOutPath))
  70. {
  71. Directory.Delete(JsonOutPath, true);
  72. }
  73. Directory.CreateDirectory(CodeOutPath);
  74. Directory.CreateDirectory(JsonOutPath);
  75. //保存配置和代码
  76. foreach (var excelData in excelDataList)
  77. {
  78. File.WriteAllText(CodeOutPath + "ExcelConfig_" + excelData.TableName + ".cs", excelData.OutCode);
  79. var config = new JsonSerializerOptions();
  80. config.WriteIndented = true;
  81. File.WriteAllText(JsonOutPath + excelData.TableName + ".json", JsonSerializer.Serialize(excelData.DataList, config));
  82. }
  83. //生成加载代码
  84. var code = GeneratorInitCode(excelDataList);
  85. File.WriteAllText(CodeOutPath + "ExcelConfig.cs", code);
  86. }
  87. catch (Exception e)
  88. {
  89. PrintError(e.ToString());
  90. return false;
  91. }
  92.  
  93. return true;
  94. }
  95.  
  96. private static string GeneratorInitCode(List<ExcelData> excelList)
  97. {
  98. var code = $"using System;\n";
  99. code += $"using System.Collections.Generic;\n";
  100. code += $"using System.Text.Json;\n";
  101. code += $"using Godot;\n";
  102. code += $"\n";
  103. code += $"namespace Config;\n";
  104. code += $"\n";
  105. code += $"public static partial class ExcelConfig\n";
  106. code += $"{{\n";
  107.  
  108. var fieldCode = "";
  109. var callFuncCode = "";
  110. var funcCode = "";
  111. foreach (var excelData in excelList)
  112. {
  113. var idName = excelData.ColumnNames[0];
  114. var idTypeStr = excelData.ColumnMappingData[idName].TypeStr;
  115. fieldCode += $" /// <summary>\n";
  116. fieldCode += $" /// {excelData.TableName}.xlsx表数据集合, 以 List 形式存储, 数据顺序与 Excel 表相同\n";
  117. fieldCode += $" /// </summary>\n";
  118. fieldCode += $" public static List<{excelData.TableName}> {excelData.TableName}_List {{ get; private set; }}\n";
  119. fieldCode += $" /// <summary>\n";
  120. fieldCode += $" /// {excelData.TableName}.xlsx表数据集合, 里 Map 形式存储, key 为 {idName}\n";
  121. fieldCode += $" /// </summary>\n";
  122. fieldCode += $" public static Dictionary<{idTypeStr}, {excelData.TableName}> {excelData.TableName}_Map {{ get; private set; }}\n";
  123. fieldCode += $"\n";
  124. callFuncCode += $" _Init{excelData.TableName}Config();\n";
  125. funcCode += $" private static void _Init{excelData.TableName}Config()\n";
  126. funcCode += $" {{\n";
  127. funcCode += $" try\n";
  128. funcCode += $" {{\n";
  129. funcCode += $" var text = _ReadConfigAsText(\"res://resource/config/{excelData.TableName}.json\");\n";
  130. funcCode += $" {excelData.TableName}_List = JsonSerializer.Deserialize<List<{excelData.TableName}>>(text);\n";
  131. funcCode += $" {excelData.TableName}_Map = new Dictionary<{idTypeStr}, {excelData.TableName}>();\n";
  132. funcCode += $" foreach (var item in {excelData.TableName}_List)\n";
  133. funcCode += $" {{\n";
  134. funcCode += $" {excelData.TableName}_Map.Add(item.{idName}, item);\n";
  135. funcCode += $" }}\n";
  136. funcCode += $" }}\n";
  137. funcCode += $" catch (Exception e)\n";
  138. funcCode += $" {{\n";
  139. funcCode += $" GD.PrintErr(e.ToString());\n";
  140. funcCode += $" throw new Exception(\"初始化表'{excelData.TableName}'失败!\");\n";
  141. funcCode += $" }}\n";
  142. funcCode += $" }}\n";
  143. }
  144.  
  145. code += fieldCode;
  146. code += $"\n";
  147. code += $" private static bool _init = false;\n";
  148. code += $" /// <summary>\n";
  149. code += $" /// 初始化所有配置表数据\n";
  150. code += $" /// </summary>\n";
  151. code += $" public static void Init()\n";
  152. code += $" {{\n";
  153. code += $" if (_init) return;\n";
  154. code += $" _init = true;\n";
  155. code += $"\n";
  156. code += callFuncCode;
  157. code += $" }}\n";
  158. code += funcCode;
  159. code += $" private static string _ReadConfigAsText(string path)\n";
  160. code += $" {{\n";
  161. code += $" var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);\n";
  162. code += $" var asText = file.GetAsText();\n";
  163. code += $" file.Dispose();\n";
  164. code += $" return asText;\n";
  165. code += $" }}\n";
  166. code += $"}}";
  167. return code;
  168. }
  169. private static ExcelData ReadExcel(string excelPath)
  170. {
  171. var excelData = new ExcelData();
  172. //文件名称
  173. var fileName = Path.GetFileNameWithoutExtension(excelPath).FirstToUpper();
  174. excelData.TableName = fileName;
  175. //输出代码
  176. var outStr = $"using System.Text.Json.Serialization;\n";
  177. outStr += $"using System.Collections.Generic;\n\n";
  178. outStr += $"namespace Config;\n\n";
  179. outStr += $"public static partial class ExcelConfig\n{{\n";
  180. outStr += $" public class {fileName}\n";
  181. outStr += $" {{\n";
  182. var sourceFile = excelPath;
  183.  
  184. //行数
  185. var rowCount = -1;
  186. //列数
  187. var columnCount = -1;
  188. //加载表数据
  189. var workbook = new XSSFWorkbook(sourceFile);
  190. using (workbook)
  191. {
  192. var sheet1 = workbook.GetSheet("Sheet1");
  193. rowCount = sheet1.LastRowNum;
  194. //先解析表中的列名, 注释, 类型
  195. var names = sheet1.GetRow(0);
  196. var descriptions = sheet1.GetRow(1);
  197. var types = sheet1.GetRow(2);
  198. columnCount = names.LastCellNum;
  199. foreach (var cell in names)
  200. {
  201. //字段名称
  202. var field = GetCellStringValue(cell);
  203. if (string.IsNullOrEmpty(field))
  204. {
  205. if (cell.ColumnIndex == 0)
  206. {
  207. throw new Exception($"表'{fileName}'的列数为0!");
  208. }
  209. //到达最后一列了
  210. columnCount = cell.ColumnIndex;
  211. break;
  212. }
  213. field = field.FirstToUpper();
  214. excelData.ColumnNames.Add(field);
  215.  
  216. var descriptionCell = descriptions.GetCell(cell.ColumnIndex);
  217. //描述
  218. string description;
  219. if (descriptionCell != null)
  220. {
  221. description = GetCellStringValue(descriptionCell).Replace("\n", " <br/>\n /// ");
  222. }
  223. else
  224. {
  225. description = "";
  226. }
  227. //类型
  228. var typeString = GetCellStringValue(types.GetCell(cell.ColumnIndex));
  229. if (string.IsNullOrEmpty(typeString))
  230. {
  231. throw new Exception($"表'{fileName}'中'{field}'这一列类型为空!");
  232. }
  233. //尝试解析类型
  234. MappingData mappingData;
  235. try
  236. {
  237. mappingData = ConvertToType(typeString.Replace(" ", ""));
  238. }
  239. catch (Exception e)
  240. {
  241. PrintError(e.ToString());
  242. throw new Exception($"表'{fileName}'中'{field}'这一列类型描述语法错误: {typeString}");
  243. }
  244. if (!excelData.ColumnMappingData.TryAdd(field, mappingData))
  245. {
  246. throw new Exception($"表'{fileName}'中存在相同名称的列: '{field}'!");
  247. }
  248. outStr += $" /// <summary>\n";
  249. outStr += $" /// {description}\n";
  250. outStr += $" /// </summary>\n";
  251. outStr += $" [JsonInclude]\n";
  252. outStr += $" public {mappingData.TypeStr} {field} {{ get; private set; }}\n\n";
  253. }
  254. outStr += " }\n";
  255. outStr += "}";
  256. //解析字段类型
  257. foreach (var kv in excelData.ColumnMappingData)
  258. {
  259. var typeName = kv.Value.TypeName;
  260. var type = Type.GetType(typeName);
  261. if (type == null)
  262. {
  263. throw new Exception($"表'{fileName}'中'{kv.Key}'这一列类型未知! " + kv.Value.TypeStr);
  264. }
  265. excelData.ColumnType.Add(kv.Key, type);
  266. }
  267.  
  268. //解析数据
  269. for (int i = 3; i <= rowCount; i++)
  270. {
  271. Dictionary<string, object> data = null;
  272. var row = sheet1.GetRow(i);
  273. if (row == null)
  274. {
  275. continue;
  276. }
  277. for (int j = 0; j < columnCount; j++)
  278. {
  279. var cell = row.GetCell(j);
  280. var strValue = GetCellStringValue(cell);
  281. //如果这一行的第一列数据为空, 则跳过这一行
  282. if (j == 0 && string.IsNullOrEmpty(strValue))
  283. {
  284. break;
  285. }
  286. else if (data == null)
  287. {
  288. data = new Dictionary<string, object>();
  289. excelData.DataList.Add(data);
  290. }
  291.  
  292. var fieldName = excelData.ColumnNames[j];
  293. var mappingData = excelData.ColumnMappingData[fieldName];
  294. try
  295. {
  296. switch (mappingData.TypeStr)
  297. {
  298. case "bool":
  299. case "boolean":
  300. data.Add(fieldName, GetCellBooleanValue(cell));
  301. break;
  302. case "byte":
  303. data.Add(fieldName, Convert.ToByte(GetCellNumberValue(cell)));
  304. break;
  305. case "sbyte":
  306. data.Add(fieldName, Convert.ToSByte(GetCellNumberValue(cell)));
  307. break;
  308. case "short":
  309. data.Add(fieldName, Convert.ToInt16(GetCellNumberValue(cell)));
  310. break;
  311. case "ushort":
  312. data.Add(fieldName, Convert.ToUInt16(GetCellNumberValue(cell)));
  313. break;
  314. case "int":
  315. data.Add(fieldName, Convert.ToInt32(GetCellNumberValue(cell)));
  316. break;
  317. case "uint":
  318. data.Add(fieldName, Convert.ToUInt32(GetCellNumberValue(cell)));
  319. break;
  320. case "long":
  321. data.Add(fieldName, Convert.ToInt64(GetCellNumberValue(cell)));
  322. break;
  323. case "ulong":
  324. data.Add(fieldName, Convert.ToUInt64(GetCellNumberValue(cell)));
  325. break;
  326. case "float":
  327. data.Add(fieldName, Convert.ToSingle(GetCellNumberValue(cell)));
  328. break;
  329. case "double":
  330. data.Add(fieldName, GetCellNumberValue(cell));
  331. break;
  332. case "string":
  333. data.Add(fieldName, GetCellStringValue(cell));
  334. break;
  335. default:
  336. {
  337. var cellStringValue = GetCellStringValue(cell);
  338. if (cellStringValue.Length == 0)
  339. {
  340. data.Add(fieldName, null);
  341. }
  342. else
  343. {
  344. data.Add(fieldName, JsonSerializer.Deserialize(cellStringValue, excelData.ColumnType[fieldName]));
  345. }
  346. }
  347. break;
  348. }
  349. }
  350. catch (Exception e)
  351. {
  352. PrintError(e.ToString());
  353. throw new Exception($"解析表'{fileName}'第'{i + 1}'行第'{j + 1}'列数据时发生异常");
  354. }
  355. }
  356. }
  357. }
  358.  
  359. excelData.OutCode = outStr;
  360. return excelData;
  361. }
  362.  
  363. private static string GetCellStringValue(ICell cell)
  364. {
  365. if (cell == null)
  366. {
  367. return "";
  368. }
  369. switch (cell.CellType)
  370. {
  371. case CellType.Numeric:
  372. return cell.NumericCellValue.ToString();
  373. case CellType.String:
  374. return cell.StringCellValue;
  375. case CellType.Formula:
  376. return cell.CellFormula;
  377. case CellType.Boolean:
  378. return cell.BooleanCellValue ? "true" : "false";
  379. }
  380.  
  381. return "";
  382. }
  383.  
  384. private static double GetCellNumberValue(ICell cell)
  385. {
  386. if (cell == null)
  387. {
  388. return 0;
  389. }
  390.  
  391. return cell.NumericCellValue;
  392. }
  393.  
  394. private static bool GetCellBooleanValue(ICell cell)
  395. {
  396. if (cell == null)
  397. {
  398. return false;
  399. }
  400.  
  401. return cell.BooleanCellValue;
  402. }
  403. private static MappingData ConvertToType(string str)
  404. {
  405. if (Regex.IsMatch(str, "^\\w+$"))
  406. {
  407. var typeStr = TypeStrMapping(str);
  408. var typeName = TypeNameMapping(str);
  409. return new MappingData(typeStr, typeName);
  410. }
  411. else if (str.StartsWith('{'))
  412. {
  413. var tempStr = str.Substring(1, str.Length - 2);
  414. var index = tempStr.IndexOf(':');
  415. if (index == -1)
  416. {
  417. throw new Exception("类型描述语法错误!");
  418. }
  419.  
  420. var keyStr = tempStr.Substring(0, index);
  421. if (!IsBaseType(keyStr))
  422. {
  423. throw new Exception($"字典key类型必须是基础类型!");
  424. }
  425. var type1 = ConvertToType(keyStr);
  426. var type2 = ConvertToType(tempStr.Substring(index + 1));
  427. var typeStr = $"Dictionary<{type1.TypeStr}, {type2.TypeStr}>";
  428. var typeName = $"System.Collections.Generic.Dictionary`2[[{type1.TypeName}],[{type2.TypeName}]]";
  429. return new MappingData(typeStr, typeName);
  430. }
  431. else if (str.StartsWith('['))
  432. {
  433. var tempStr = str.Substring(1, str.Length - 2);
  434. var typeData = ConvertToType(tempStr);
  435. var typeStr = typeData.TypeStr + "[]";
  436. var typeName = typeData.TypeName + "[]";
  437. return new MappingData(typeStr, typeName);
  438. }
  439. throw new Exception("类型描述语法错误!");
  440. }
  441. private static string TypeStrMapping(string typeName)
  442. {
  443. switch (typeName)
  444. {
  445. case "boolean": return "bool";
  446. case "vector2": return "SerializeVector2";
  447. case "vector3": return "SerializeVector3";
  448. case "color": return "SerializeColor";
  449. }
  450.  
  451. return typeName;
  452. }
  453.  
  454. private static string TypeNameMapping(string typeName)
  455. {
  456. switch (typeName)
  457. {
  458. case "bool":
  459. case "boolean": return typeof(bool).FullName;
  460. case "byte": return typeof(byte).FullName;
  461. case "sbyte": return typeof(sbyte).FullName;
  462. case "short": return typeof(short).FullName;
  463. case "ushort": return typeof(ushort).FullName;
  464. case "int": return typeof(int).FullName;
  465. case "uint": return typeof(uint).FullName;
  466. case "long": return typeof(long).FullName;
  467. case "ulong": return typeof(ulong).FullName;
  468. case "string": return typeof(string).FullName;
  469. case "float": return typeof(float).FullName;
  470. case "double": return typeof(double).FullName;
  471. case "vector2": return "SerializeVector2";
  472. case "vector3": return "SerializeVector3";
  473. case "color": return "SerializeColor";
  474. }
  475.  
  476. return typeName;
  477. }
  478.  
  479. private static bool IsBaseType(string typeName)
  480. {
  481. switch (typeName)
  482. {
  483. case "bool":
  484. case "boolean":
  485. case "byte":
  486. case "sbyte":
  487. case "short":
  488. case "ushort":
  489. case "int":
  490. case "uint":
  491. case "long":
  492. case "ulong":
  493. case "string":
  494. case "float":
  495. case "double":
  496. return true;
  497. }
  498.  
  499. return false;
  500. }
  501. private static void PrintError(string message)
  502. {
  503. Console.ForegroundColor = ConsoleColor.Red;
  504. Console.WriteLine(message);
  505. Console.ResetColor();
  506. }
  507. /// <summary>
  508. /// 字符串首字母小写
  509. /// </summary>
  510. public static string FirstToLower(this string str)
  511. {
  512. return str.Substring(0, 1).ToLower() + str.Substring(1);
  513. }
  514. /// <summary>
  515. /// 字符串首字母大写
  516. /// </summary>
  517. public static string FirstToUpper(this string str)
  518. {
  519. return str.Substring(0, 1).ToUpper() + str.Substring(1);
  520. }
  521. }