ExcelDataReader.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using System.IO;
  5. using Excel;
  6. using System.Reflection;
  7. using System;
  8. using System.Linq;
  9. public class ExcelDataReader
  10. {
  11. //Excel第1行对应特殊标记
  12. private const int specialSignRow = 0;
  13. //Excel第2行对应中文说明
  14. private const int excelNodeRow = 1;
  15. //Excel第3行对应字段名称
  16. private const int excelNameRow = 2;
  17. //Excel第4行对应字段类型
  18. private const int excelTypeRow = 3;
  19. //Excel第5行及以后对应字段值
  20. private const int excelDataRow = 4;
  21. //标记注释行/列
  22. private const string annotationSign = "//";
  23. #region --- Read Excel ---
  24. //创建Excel对应的C#类
  25. public static void ReadAllExcelToCode(string allExcelPath,string codeSavePath)
  26. {
  27. //读取所有Excel文件
  28. //指定目录中与指定的搜索模式和选项匹配的文件的完整名称(包含路径)的数组;如果未找到任何文件,则为空数组。
  29. string[] excelFileFullPaths = Directory.GetFiles(allExcelPath,"*.xlsx");
  30. if(excelFileFullPaths == null || excelFileFullPaths.Length == 0)
  31. {
  32. Debug.Log("Excel file count == 0");
  33. return;
  34. }
  35. //遍历所有Excel,创建C#类
  36. for(int i = 0; i < excelFileFullPaths.Length; i++)
  37. {
  38. ReadOneExcelToCode(excelFileFullPaths[i],codeSavePath);
  39. }
  40. }
  41. //创建Excel对应的C#类
  42. public static void ReadOneExcelToCode(string excelFullPath,string codeSavePath)
  43. {
  44. //解析Excel获取中间数据
  45. ExcelMediumData excelMediumData = CreateClassCodeByExcelPath(excelFullPath);
  46. if(excelMediumData == null)
  47. {
  48. Debug.LogError($"读取Excel失败 : {excelFullPath}");
  49. return;
  50. }
  51. if(!excelMediumData.isValid)
  52. {
  53. Debug.LogError($"读取Excel失败,Excel标记失效 : {excelMediumData.excelName}");
  54. return;
  55. }
  56. if(!excelMediumData.isCreateCSharp && !excelMediumData.isCreateAssignment)
  57. {
  58. Debug.LogError($"读取Excel失败,Excel不允许生成CSCode : {excelMediumData.excelName}");
  59. return;
  60. }
  61. //根据数据生成C#脚本
  62. string classCodeStr = ExcelCodeCreater.CreateCodeStrByExcelData(excelMediumData);
  63. if(string.IsNullOrEmpty(classCodeStr))
  64. {
  65. Debug.LogError($"解析Excel失败 : {excelMediumData.excelName}");
  66. return;
  67. }
  68. //检查导出路径
  69. if(!Directory.Exists(codeSavePath))
  70. Directory.CreateDirectory(codeSavePath);
  71. //类名
  72. string codeFileName = excelMediumData.excelName + "ExcelData";
  73. //写文件,生成CS类文件
  74. StreamWriter sw = new StreamWriter($"{codeSavePath}/{codeFileName}.cs");
  75. sw.WriteLine(classCodeStr);
  76. sw.Close();
  77. //
  78. UnityEditor.AssetDatabase.SaveAssets();
  79. UnityEditor.AssetDatabase.Refresh();
  80. //
  81. Debug.Log($"生成Excel的CS成功 : {excelMediumData.excelName}");
  82. }
  83. #endregion
  84. #region --- Create Asset ---
  85. //创建Excel对应的Asset数据文件
  86. public static void CreateAllExcelAsset(string allExcelPath,string assetSavePath)
  87. {
  88. //读取所有Excel文件
  89. //指定目录中与指定的搜索模式和选项匹配的文件的完整名称(包含路径)的数组;如果未找到任何文件,则为空数组。
  90. string[] excelFileFullPaths = Directory.GetFiles(allExcelPath,"*.xlsx");
  91. if(excelFileFullPaths == null || excelFileFullPaths.Length == 0)
  92. {
  93. Debug.Log("Excel file count == 0");
  94. return;
  95. }
  96. //遍历所有Excel,创建Asset
  97. for(int i = 0; i < excelFileFullPaths.Length; i++)
  98. {
  99. CreateOneExcelAsset(excelFileFullPaths[i],assetSavePath);
  100. }
  101. }
  102. //创建Excel对应的Asset数据文件
  103. public static void CreateOneExcelAsset(string excelFullPath,string assetSavePath)
  104. {
  105. //解析Excel获取中间数据
  106. ExcelMediumData excelMediumData = CreateClassCodeByExcelPath(excelFullPath);
  107. if(excelMediumData == null)
  108. {
  109. Debug.LogError($"读取Excel失败 : {excelFullPath}");
  110. return;
  111. }
  112. if(!excelMediumData.isValid)
  113. {
  114. Debug.LogError($"读取Excel失败,Excel标记失效 : {excelMediumData.excelName}");
  115. return;
  116. }
  117. if(!excelMediumData.isCreateAsset)
  118. {
  119. Debug.LogError($"读取Excel失败,Excel不允许生成Asset : {excelMediumData.excelName}");
  120. return;
  121. }
  122. ////获取当前程序集
  123. //Assembly assembly = Assembly.GetExecutingAssembly();
  124. ////创建类的实例,返回为 object 类型,需要强制类型转换,assembly.CreateInstance("类的完全限定名(即包括命名空间)");
  125. //object class0bj = assembly.CreateInstance(excelMediumData.excelName + "Assignment",true);
  126. //必须遍历所有程序集来获得类型。当前在Assembly-CSharp-Editor中,目标类型在Assembly-CSharp中,不同程序将无法获取类型
  127. Type assignmentType = null;
  128. string assetAssignmentName = excelMediumData.excelName + "AssetAssignment";
  129. foreach(var asm in AppDomain.CurrentDomain.GetAssemblies())
  130. {
  131. //查找目标类型
  132. Type tempType = asm.GetType(assetAssignmentName);
  133. if(tempType != null)
  134. {
  135. assignmentType = tempType;
  136. break;
  137. }
  138. }
  139. if(assignmentType == null)
  140. {
  141. Debug.LogError($"创界Asset失败,未找到Asset生成类 : {excelMediumData.excelName}");
  142. return;
  143. }
  144. //反射获取方法
  145. MethodInfo methodInfo = assignmentType.GetMethod("CreateAsset");
  146. if(methodInfo == null)
  147. {
  148. if(assignmentType == null)
  149. {
  150. Debug.LogError($"创界Asset失败,未找到Asset创建函数 : {excelMediumData.excelName}");
  151. return;
  152. }
  153. }
  154. methodInfo.Invoke(null,new object[] { excelMediumData,assetSavePath , excelFullPath });
  155. //创建Asset文件成功
  156. Debug.Log($"生成Excel的Asset成功 : {excelMediumData.excelName}"+ excelFullPath);
  157. }
  158. #endregion
  159. #region --- private ---
  160. //解析Excel,创建中间数据
  161. private static ExcelMediumData CreateClassCodeByExcelPath(string excelFileFullPath)
  162. {
  163. if(string.IsNullOrEmpty(excelFileFullPath))
  164. return null;
  165. excelFileFullPath = excelFileFullPath.Replace("\\","/");
  166. //读取Excel
  167. FileStream stream = File.Open(excelFileFullPath,FileMode.Open,FileAccess.Read);
  168. if(stream == null)
  169. return null;
  170. //解析Excel
  171. IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
  172. //无效Excel
  173. if(excelReader == null || !excelReader.IsValid)
  174. {
  175. Debug.Log("Invalid excel : " + excelFileFullPath);
  176. return null;
  177. }
  178. Debug.Log("开始解析Excel : " + excelReader.Name);
  179. //记录Excel数据
  180. ExcelMediumData excelMediumData = new ExcelMediumData();
  181. //Excel名字
  182. excelMediumData.excelName = excelReader.Name;
  183. //当前遍历的行
  184. int curRowIndex = 0;
  185. //开始读取,按行遍历
  186. while(excelReader.Read())
  187. {
  188. //这一行没有读取到数据,视为无效行数据
  189. if(excelReader.FieldCount <= 0)
  190. {
  191. curRowIndex++;
  192. continue;
  193. }
  194. //读取每一行的完整数据
  195. string[] datas = new string[excelReader.FieldCount];
  196. for(int j = 0; j < excelReader.FieldCount; ++j)
  197. {
  198. //可以直接读取指定类型数据,不过只支持有限数据类型,这里统一读取string,然后再数据转化
  199. //excelReader.GetInt32(j); excelReader.GetFloat(j);
  200. //读取每一个单元格数据
  201. datas[j] = excelReader.GetString(j);
  202. }
  203. switch(curRowIndex)
  204. {
  205. case specialSignRow:
  206. //特殊标记行
  207. string specialSignStr = datas[0];
  208. if(specialSignStr.Length >= 4)
  209. {
  210. excelMediumData.isValid = specialSignStr[0] == 'T';
  211. excelMediumData.isCreateCSharp = specialSignStr[1] == 'T';
  212. excelMediumData.isCreateAssignment = specialSignStr[2] == 'T';
  213. excelMediumData.isCreateAsset = specialSignStr[3] == 'T';
  214. }
  215. else
  216. {
  217. Debug.LogError("未解析到特殊标记");
  218. }
  219. break;
  220. case excelNodeRow:
  221. //数据注释行
  222. excelMediumData.propertyNodeArray = datas;
  223. break;
  224. case excelNameRow:
  225. //数据名称行
  226. excelMediumData.propertyNameArray = datas;
  227. //注释列号
  228. for(int i = 0; i < datas.Length; i++)
  229. {
  230. if(string.IsNullOrEmpty(datas[i]) || datas[i].StartsWith(annotationSign))
  231. excelMediumData.annotationColList.Add(i);
  232. }
  233. break;
  234. case excelTypeRow:
  235. //数据类型行
  236. excelMediumData.propertyTypeArray = datas;
  237. break;
  238. default:
  239. //数据内容行
  240. excelMediumData.allRowItemList.Add(datas);
  241. //注释行号
  242. if(string.IsNullOrEmpty(datas[0]) || datas[0].StartsWith(annotationSign))
  243. excelMediumData.annotationRowList.Add(excelMediumData.allRowItemList.Count - 1);
  244. break;
  245. }
  246. //
  247. curRowIndex++;
  248. }
  249. if(CheckExcelMediumData(ref excelMediumData))
  250. {
  251. Debug.Log("读取Excel成功");
  252. return excelMediumData;
  253. }
  254. else
  255. {
  256. Debug.LogError("读取Excel失败");
  257. return null;
  258. }
  259. }
  260. //校验Excel数据
  261. private static bool CheckExcelMediumData(ref ExcelMediumData mediumData)
  262. {
  263. if(mediumData == null)
  264. return false;
  265. //检查数据有效性
  266. if(!mediumData.isValid)
  267. {
  268. Debug.LogError("Excel被标记无效");
  269. return false;
  270. }
  271. if(string.IsNullOrEmpty(mediumData.excelName))
  272. {
  273. Debug.LogError("Excel名字为空");
  274. return false;
  275. }
  276. if(mediumData.propertyNameArray == null || mediumData.propertyNameArray.Length == 0)
  277. {
  278. Debug.LogError("未解析到数据名称");
  279. return false;
  280. }
  281. if(mediumData.propertyTypeArray == null || mediumData.propertyTypeArray.Length == 0)
  282. {
  283. Debug.LogError("未解析到数据类型");
  284. return false;
  285. }
  286. if(mediumData.propertyNameArray.Length != mediumData.propertyTypeArray.Length)
  287. {
  288. Debug.LogError("数据名称与数据类型数量不一致");
  289. return false;
  290. }
  291. if(mediumData.allRowItemList.Count == 0)
  292. {
  293. Debug.LogError("数据内容为空");
  294. return false;
  295. }
  296. if(mediumData.propertyNameArray[0] != "id")
  297. {
  298. Debug.LogError("第一个字段必须是id字段");
  299. return false;
  300. }
  301. return true;
  302. }
  303. #endregion
  304. }