通过之前写的MonsterDataReg.xml这个reg文件,把我们的MonsterData.xml映射进一个Excel里面,其实本质上就是MonsterData类映射到Excel。

创建读取Reg.Xml的中间类

DataEditor中添加VarClass中间类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// 用来读取Reg.xml中variable的中间类
/// </summary>
public class VarClass
{
/// <summary>
/// 对应的是变量名
/// </summary>
public string Name { get; set; }
public string Type { get; set; }
public string Col { get; set; }
public string DefaultValue { get; set; }
public string Foreign { get; set; }
public string Split { get; set; }
/// <summary>
/// 如果此variable的Type是list,对应的list的类名
/// </summary>
public string ListClassName { get; set; }
/// <summary>
/// 如果此variable的Type是list,对应list的sheet名
/// </summary>
public string ListSheetName { get; set; }
}

关于这些变量的含义请参考“类与Excel转换”那一节

DataEditor中添加SheetClass中间类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// <summary>
/// 用来读取Reg.xml中List的中间类
/// </summary>
public class SheetClass
{
public VarClass ParentVar { get;set; }//父Node的变量对应的VarClass
/// <summary>
/// Sheet的层级,用于递归写入Excel时准确赋值
/// </summary>
public int Depth { get; set; }
/// <summary>
/// 对应的是list容纳的类名
/// </summary>
public string Name { get; set; }
public string SheetName { get; set; }
public string MainKey { get; set; }
public string Split { get; set; }
public List<VarClass> VarList = new List<VarClass>();//子Node
}

关于这些变量的含义请参考“类与Excel转换”那一节

创建写入Excel的中间类

DataEditor中添加SheetDataRowData中间类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <summary>
/// 用来写入Excel的中间类
/// </summary>
public class SheetData
{
/// <summary>
/// 一个Sheet所有的列名,用在Excel读取时这里是变量名
/// </summary>
public List<string> AllColName = new List<string>();
/// <summary>
/// 一个Sheet每列对应的Type
/// </summary>
public List<string> AllType = new List<string>();
/// <summary>
/// 一个Sheet有多少行以及每列对应的值
/// </summary>
public List<RowData> AllRowData = new List<RowData>();
}
/// <summary>
/// 用来写入Excel每行内容的中间类
/// </summary>
public class RowData
{
/// <summary>
/// 用在Excel转Xml时,记录下ForeignID指向的MainKey对应的值
/// </summary>
public string ParentValue = "";
/// <summary>
/// Key是对应的列名
/// </summary>
public Dictionary<string,string> RowDataDic = new Dictionary<string,string>();
}

Reg.xml的读取

这里我们封装一个递归读取Reg.xml的方法,因为xml本身是一个树结构,所以利用递归来读取。

DataEditor中添加ReadRegXmlNode方法,递归读取RegXml,并根据里面的list结点的信息把sheet输出到allSheetClassDic字典里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/// <summary>
/// 递归读取RegXml的list
/// </summary>
/// <param name="xmlElement">一个XmlList根结点</param>
private static void ReadRegXmlNode(XmlElement xmlElement,Dictionary<string,SheetClass> allSheetClassDic,int depth)
{
depth++;
foreach (XmlElement xmlEle in xmlElement.ChildNodes)
{
if (xmlEle.GetAttribute("type") == "list")//如果此variable是List,那么它第一个ChildNode肯定是关于list的信息的
{
XmlElement listEle = xmlEle.FirstChild as XmlElement;
VarClass parentVar = new VarClass()
{
Name = xmlEle.GetAttribute("name"),
Type = xmlEle.GetAttribute("type"),
Col = xmlEle.GetAttribute("col"),
DefaultValue = xmlEle.GetAttribute("defaultValue"),
Foreign = xmlEle.GetAttribute("foreign"),
Split = xmlEle.GetAttribute("split"),
ListClassName = listEle.GetAttribute("name"),
ListSheetName = listEle.GetAttribute("sheetname"),
};

SheetClass sheetClass = new SheetClass()
{
ParentVar = parentVar,
Depth = depth,
Name = listEle.GetAttribute("name"),
SheetName = listEle.GetAttribute("sheetname"),
Split = listEle.GetAttribute("split"),
MainKey = listEle.GetAttribute("mainKey"),
};
if (!string.IsNullOrEmpty(sheetClass.SheetName))
{
if (!allSheetClassDic.ContainsKey(sheetClass.SheetName))
{
//获取该ListEle下所有的node,这些node肯定都是variable
foreach (XmlElement listChildEle in listEle.ChildNodes)
{
VarClass childVar = new VarClass()
{
Name = listChildEle.GetAttribute("name"),
Type = listChildEle.GetAttribute("type"),
Col = listChildEle.GetAttribute("col"),
DefaultValue = listChildEle.GetAttribute("defaultValue"),
Foreign = xmlEle.GetAttribute("foreign"),
Split = listChildEle.GetAttribute("split")
};
if (childVar.Type == "list")
{
childVar.ListClassName = ((XmlElement)listChildEle.FirstChild).GetAttribute("name");
childVar.ListSheetName = ((XmlElement)listChildEle.FirstChild).GetAttribute("sheetname");
}
sheetClass.VarList.Add(childVar);
}
allSheetClassDic.Add(sheetClass.SheetName, sheetClass);
}
}
ReadRegXmlNode(listEle, allSheetClassDic,depth);//递归查询listEle下的list
}
}
}

目前的Reg.xml读取方式只支持外层节点(也就是根节点“data”内部的第一层子节点)type必须都是“list”的情况,所以我们写的数据类MonsterData里面的最外层的变量都是list(MonsterData见“配置表准备”一节),我们必须准备一个方法像递归查询Reg.xml一样递归读取从MonsterData.xml反序列化出来的MonsterData类,就是下面的数据类读取的MapDataToSheet方法

DataEditor中添加ReadRegXmlNode方法,这个方法用来读取Reg.xml并返回赋值好的allSheetClassDic字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static Dictionary<string, SheetClass> ReadRegToDic(string xmlRegName, ref string excelName, ref string xmlName, ref string className)
{
string regPath = RegPath + xmlRegName + ".xml";
if (!File.Exists(regPath))
{
Debug.LogErrorFormat("指定的XmlReg不存在:{0}", xmlRegName);
return null;
}

XmlDocument xmlDocument = new XmlDocument();
XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
xmlReaderSettings.IgnoreComments = true;//忽略注释
XmlReader xmlReader = XmlReader.Create(regPath, xmlReaderSettings);
xmlDocument.Load(xmlReader);
XmlNode rootNode = xmlDocument.SelectSingleNode("data");
XmlElement rootEle = rootNode as XmlElement;
className = rootEle.GetAttribute("name");
excelName = rootEle.GetAttribute("from");
xmlName = rootEle.GetAttribute("to");
//储存所有Reg中的Sheet信息的Dic
Dictionary<string, SheetClass> allSheetClassDic = new Dictionary<string, SheetClass>();

ReadRegXmlNode(rootEle, allSheetClassDic, 0);
xmlReader.Close();
return allSheetClassDic;
}

数据类的读取

将Reg.xml读取出来后,我们也要将反序列化后的有一定原始数据的数据类也读取出来,同样地,数据类可能有list的嵌套,所以我们也要递归读取,将读取出来的数据都写进SheetData中间类里,并保存在sheetDataDic字典中

DataEditor中添加MapDataToSheet方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/// <summary>
/// 将反射出来的数据写进SheetData类字典里,这是一个递归方法
/// </summary>
/// <param name="data">从Xml反序列化出来的类数据</param>
/// <param name="rootSheet">Reg.Xml中每种list的枝干的SheetClass</param>
/// <param name="sheetClassDic">缓存的sheetClassDic</param>
/// <param name="sheetDataDic">需要输出的sheetDataDic</param>
private static void MapDataToSheet(object data, SheetClass rootSheet, Dictionary<string, SheetClass> sheetClassDic, Dictionary<string, SheetData> sheetDataDic)
{
List<VarClass> childVars = rootSheet.VarList;
VarClass parentVar = rootSheet.ParentVar;

object dataList = GetMemberValue(data, parentVar.Name);//反射出数据类中对应的list变量
//反射出list的Count
int listCount =System.Convert.ToInt32(dataList.GetType().InvokeMember("get_Count", BindingFlags.Default | BindingFlags.InvokeMethod, null, dataList, new object[] { }));

SheetData sheetData = new SheetData();
childVars.ForEach(item =>
{
if (!string.IsNullOrEmpty(item.Col))
{
sheetData.AllColName.Add(item.Col); //将SheetData的每列的表头加进去
sheetData.AllType.Add(item.Type);
}
});
for (int i = 0; i < listCount; i++)
{
//item获取的是一行数据,我们根据Reg中存储的对应的变量名来获取具体数据
object item = dataList.GetType().InvokeMember("get_Item",BindingFlags.Default | BindingFlags.InvokeMethod,null,dataList,new object[] { i });

RowData rowData = new RowData();

childVars.ForEach(childVar =>
{
if(childVar.Type == "list")
{
SheetClass tempSheetClass = sheetClassDic[childVar.ListSheetName];
MapDataToSheet(item, tempSheetClass, sheetClassDic, sheetDataDic);//递归获取所有dataList
}
else//如果此节点不是list
{
object value = GetMemberValue(item, childVar.Name);//在这里获取具体变量
if(value != null)
{
rowData.RowDataDic.Add(childVar.Col,value.ToString());
}
else
{
Debug.LogError(childVar.Name + "反射为空,请检查Reg.xml是否出错");
}
}
});
//string key = parentVar.ListClassName +"&"+ parentVar.ListSheetName;//出现重复类名或重复sheet名时使用这个key
string key = parentVar.ListSheetName;
if(sheetDataDic.ContainsKey(key))
{
sheetDataDic[key].AllRowData.Add(rowData);
}
else
{
sheetData.AllRowData.Add(rowData);
sheetDataDic.Add(key, sheetData);
}
}
}

xml转Excel

有了上面的准备,就可以将xml转Excel了

基本流程是

  1. 首先将对应的Reg.xml文件读取出来,确定写入规则,在这一步我们将得到一个sheetClass字典,以及此reg文件对应的数据类名(className)、Excel文件名(excelName)、xml文件名(xmlName)
  2. 将xml反序列化,得到一个有初始数据的数据类。
  3. 配合sheetClass读取数据类,将读取出来的结果写入sheetData字典。
  4. 将sheetData字典的内容写入Excel

DataEditor中添加XmlToExcel方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
  [MenuItem("Assets/Xml转Excel")]
public static void AssetsXmlToExcel()
{
UnityEngine.Object[] objs = Selection.objects;
for (int i = 0; i < objs.Length; i++)
{
EditorUtility.DisplayProgressBar("已经选中的xml转成Excel", "正在扫描" + objs[i].name + "……", 1.0f / objs.Length * i);
XmlToExcel(objs[i].name + "Reg");
}
EditorUtility.ClearProgressBar();
}

private static void XmlToExcel(string xmlRegName)
{
string className = string.Empty;
string excelName = string.Empty;
string xmlName = string.Empty;

Dictionary<string, SheetClass> allSheetClassDic = ReadRegToDic(xmlRegName,ref excelName,ref xmlName,ref className);

//读取对应的类(按照className)的Xml,得到一个带有数据的类
object data = GetObjFromXml(className);

//获取所有深度为1的sheetClass,并让它们传入MapDataToSheet方法
//这是因为Reg.xml的结构中,根结点没有parentVar,直接传入MapDataToSheet不可用
List<SheetClass> rootSheets = new List<SheetClass>();
foreach (SheetClass sheets in allSheetClassDic.Values)
{
if (sheets.Depth == 1)
{
rootSheets.Add(sheets);
}
}
//储存所有需要写入Excel的SheetData的Dic
Dictionary<string, SheetData> sheetDataDic = new Dictionary<string, SheetData>();
rootSheets.ForEach(sheet => { MapDataToSheet(data, sheet,allSheetClassDic,sheetDataDic); });

string excelPath = ExcelPath + excelName;
if (FileIsUsed(excelPath))
{
Debug.LogErrorFormat("{0} Excel文件已经被占用,无法修改",excelName);
return;
}
try
{
FileInfo excelInfo = new FileInfo(excelPath);
if(excelInfo.Exists)
{
excelInfo.Delete();
excelInfo = new FileInfo(excelPath);
}
using(ExcelPackage package = new ExcelPackage(excelInfo))
{
foreach (string sheetName in sheetDataDic.Keys)
{
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add(sheetName);
SheetData sheetData = sheetDataDic[sheetName];
//填充Excel每列表头
for (int i = 0; i < sheetData.AllColName.Count; i++)
{
ExcelRange range = worksheet.Cells[1, i + 1];
range.Value = sheetData.AllColName[i];
range.AutoFitColumns();//先填充值再设置自动Fit
}
//填充Excel每一行数据
for (int i = 0; i < sheetData.AllRowData.Count; i++)
{
RowData rowData = sheetData.AllRowData[i];
for (int j = 0; j < rowData.RowDataDic.Count; j++)
{
ExcelRange range = worksheet.Cells[i + 2, j + 1];
string value = rowData.RowDataDic[sheetData.AllColName[j]];
range.Value = value;
range.AutoFitColumns();
if (value.Contains("\n") || value.Contains("\r\n"))
{
range.Style.WrapText = true;
}
}
}
worksheet.Cells.AutoFitColumns();
}
package.Save();
}
}
catch (Exception e)
{
Debug.LogError(e.Message);
return;
}
Debug.Log("生成" + excelPath + "成功");
}
/// <summary>
/// 检定当前文件是否被占用
/// </summary>
/// <param name="path">文件路径</param>
/// <returns>是否被占用</returns>
private static bool FileIsUsed(string path)
{
bool result = false;
if(!File.Exists(path))
{
result = false;
}
else
{
FileStream fs = null;
try
{
fs = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
result = false;
}
catch (Exception e)
{
Debug.LogError(e);
result = true;
}
finally
{
if (fs != null)
{
fs.Close();
}
}
}
return result;
}