在项目中,需要从Excel表中提取翻译,使用工具的话相比手动复制粘贴快太多了,工作量从小时计算缩小到以秒计算,简直不要太方便。
在使用ClosedXml前需要先下载ClosedXml的库。在VS中从里"工具"--->“NuGet包管理器”--->“管理解决方案的NuGet程序包”,然后在浏览标签下输入ClosedXml,然后下载。下载好后,相应的文件会出现在工程的Packages下,从Packages文件夹下找到“ClosedXML.dll”,“DocumentFormat.OpenXml.dll”,“ExcelNumberFormat.dll”这三个程序集,然后放到Plugins目录下(Packages下相应的文件夹可以删除了)。下载后库后下面开始从Excel提取内容写入Xml中。
1、从Excel表中把内容提取出来
using (XLWorkbook wb = new XLWorkbook(excelPath))
{
//Excel表中的所有工作表
IXLWorksheets worksheets = wb.Worksheets;
int index = 0;
foreach (var worksheet in worksheets)
{
//给工作表规定一个顺序,以免混乱
if (index >= workSheetNames.Length || !worksheet.Name.Equals(workSheetNames[index]))
{
Debug.LogError("Excel表顺序不对!!");
break;
}
index++;
if (!EmptyRowCheck(worksheet, 1)) break;
col1StrList = new List();
col2StrList = new List();
//第一列中文
col1StrList = ReadColStrList(worksheet, 1);
col1StrLists.Add(col1StrList);
//第二列英文
col2StrList = ReadColStrList(worksheet, 2);
col2StrLists.Add(col2StrList);
}
}
2、这里是判断有没有空行的问题,第一列中文是基准,不能有空行,不然造成混乱
//第一列中文从起始行到结束行是否有空行
private static bool EmptyRowCheck(IXLWorksheet worksheet, int column)
{
if(worksheet == null)
{
Debug.LogError("工作表为空!!");
return false;
}
for(int i = startIndex; i <= endIndex; i++)
{
var cell = worksheet.Cell(i, column);
if (string.IsNullOrEmpty(cell.Value.ToString()))
{
Debug.LogError(worksheet.Name + "第" + column + "列" + "第" + i + "行为空值!!");
return false;
}
}
return true;
}
3、从工作表中按行、列读取内容
private static List ReadColStrList(IXLWorksheet worksheet, int column)
{
if (worksheet == null)
{
Debug.LogError("工作表为空!!");
return null;
}
List strs = new List();
for(int i = startIndex; i <= endIndex; i++)
{
//取某一列从起始行到结束行的内容,如果是用worksheet.CellsUsed();
//或者worksheet.Cells();就会跳过空行
var cell = worksheet.Cell(i, column);
strs.Add(cell.Value.ToString());
}
return strs;
}
4、通过上面的步骤就把文档中所有工作表的相关内容内容取出来了,然后开始写入XML中。这里用System.Xml.Linq库来读写XML。
private static void WriteXml()
{
for (int i = 0; i < col1StrLists.Count; i++)
{
if (i >= xmlNames.Length) break;
XElement xmlDoc = ReadXML(i);
//写一下模块的注释
WriteComment(xmlDoc, modelTitle);
for (int j = 0; j < Enum.GetNames(typeof(Keys)).Length; j++)
{
if (j >= col1StrLists[i].Count) break;
WriteString(xmlDoc, Enum.GetNames(typeof(Keys))[j], col2StrLists[i][j], col1StrLists[i][j], xmlNames[i]);
}
//结束时再写一下模块的注释
WriteComment(xmlDoc, modelTitle);
}
}
5、写注释的方法,就是这个格式的“”
private static void WriteComment(XElement xmlDoc, string value)
{
XComment xComment = new XComment(value);
xmlDoc.Add(xComment);
xmlDoc.Save(xmlPath);
}
6、写内容的方法
private static void WriteString(XElement xmlDoc, string key, string value, string comment, string xmlName)
{
//先判断一下有没有已经存在的Key
if (localizaleData.ContainsKey(key))
{
Debug.LogError(xmlName + " contains this key:" + key);
return;
}
WriteComment(xmlDoc, comment);
XElement xe = new XElement("string");
xe.SetAttributeValue("name", key);
//这里把翻译中的占位符换成{0}的形式
for(int i = 0; i < replaceFlags.Length; i++)
{
if(value.IndexOf(replaceFlags[i]) >= 0)
value = value.Replace(replaceFlags[i], "{" + i + "}");
}
xe.SetValue(value);
xmlDoc.Add(xe);
xmlDoc.Save(xmlPath);
}
7、下面是全部的代码了,这里做测试的只建了一个工作表,实际是支持N个工作表的。另外这个脚本是放在Editor文件夹下的。
public class ExcelToXml
{
//模块名称
static string modelTitle = "模块名称";
enum Keys
{
key1,
key2,
key3
}
static int startIndex = 2;//起始行序号
static int endIndex = 4;//结束行序号
static string excelPath = @"E:\Test.xlsx";//Excel表路径及文件名+后缀(只支持.xlsx)
static string[] xmlNames = new string[] { "English"};
static string[] workSheetNames = new string[] {"英语" };
static string[] replaceFlags = new string[] { "AA", "BB", "CC", "DD", "EE"};
static Dictionary localizaleData = new Dictionary();
static string xmlPath;
static List col1StrList;
static List col2StrList;
static List> col1StrLists = new List>();
static List> col2StrLists = new List>();
[MenuItem("Assets/ExcelToXml(关闭文档再执行)")]
static void ExcelToXmlTool()
{
ReadWorkSheet();
}
private static void ReadWorkSheet()
{
if (!ValidCheck()) return;
using (XLWorkbook wb = new XLWorkbook(excelPath))
{
IXLWorksheets worksheets = wb.Worksheets;
int index = 0;
foreach (var worksheet in worksheets)
{
if (index >= workSheetNames.Length || !worksheet.Name.Equals(workSheetNames[index]))
{
Debug.LogError("Excel表顺序不对!!");
break;
}
index++;
if (!EmptyRowCheck(worksheet, 1)) break;
col1StrList = new List();
col2StrList = new List();
//第一列中文
col1StrList = ReadColStrList(worksheet, 1);
col1StrLists.Add(col1StrList);
//第二列英文
col2StrList = ReadColStrList(worksheet, 2);
col2StrLists.Add(col2StrList);
}
}
WriteXml();
Debug.LogError("Excel To Xml Complete!!");
}
//第一列中文从起始行到结束行是否有空行
private static bool EmptyRowCheck(IXLWorksheet worksheet, int column)
{
if(worksheet == null)
{
Debug.LogError("工作表为空!!");
return false;
}
for(int i = startIndex; i <= endIndex; i++)
{
var cell = worksheet.Cell(i, column);
if (string.IsNullOrEmpty(cell.Value.ToString()))
{
Debug.LogError(worksheet.Name + "第" + column + "列" + "第" + i + "行为空值!!");
return false;
}
}
return true;
}
private static bool ValidCheck()
{
bool isValid = true;
if (Enum.GetNames(typeof(Keys)).Length == 0)
{
Debug.LogError("请输入Key!!");
return isValid = false;
}
if (string.IsNullOrEmpty(excelPath))
{
Debug.LogError("请输入Excel表路径!!");
return isValid = false;
}
if (!File.Exists(excelPath))
{
Debug.LogError("Excel表不存在!!");
return isValid = false;
}
if (!Path.GetExtension(excelPath).Equals(".xlsx"))
{
Debug.LogError("Excel表类型不是xlsx!!");
return isValid = false;
}
return isValid;
}
private static List ReadColStrList(IXLWorksheet worksheet, int column)
{
if (worksheet == null)
{
Debug.LogError("工作表为空!!");
return null;
}
List strs = new List();
for(int i = startIndex; i <= endIndex; i++)
{
var cell = worksheet.Cell(i, column);
strs.Add(cell.Value.ToString());
}
return strs;
}
private static XElement ReadXML(int index)
{
localizaleData.Clear();
XElement xmlDoc;
string fileName = "strings_" + xmlNames[index] + ".xml";
xmlPath = Application.dataPath + "/Localization" + "/" + fileName;
string text = File.ReadAllText(xmlPath);
xmlDoc = XElement.Parse(text);
IEnumerable elements =
from el in xmlDoc.Elements()
select el;
foreach (XElement el in elements)
{
XAttribute xat = el.FirstAttribute;
localizaleData.Add(xat.Value, el.Value);
}
return xmlDoc;
}
private static void WriteXml()
{
for (int i = 0; i < col1StrLists.Count; i++)
{
if (i >= xmlNames.Length) break;
XElement xmlDoc = ReadXML(i);
WriteComment(xmlDoc, modelTitle);
for (int j = 0; j < Enum.GetNames(typeof(Keys)).Length; j++)
{
if (j >= col1StrLists[i].Count) break;
WriteString(xmlDoc, Enum.GetNames(typeof(Keys))[j], col2StrLists[i][j], col1StrLists[i][j], xmlNames[i]);
}
WriteComment(xmlDoc, modelTitle);
}
}
private static void WriteString(XElement xmlDoc, string key, string value, string comment, string xmlName)
{
if (localizaleData.ContainsKey(key))
{
Debug.LogError(xmlName + " contains this key:" + key);
return;
}
WriteComment(xmlDoc, comment);
XElement xe = new XElement("string");
xe.SetAttributeValue("name", key);
for(int i = 0; i < replaceFlags.Length; i++)
{
if(value.IndexOf(replaceFlags[i]) >= 0)
value = value.Replace(replaceFlags[i], "{" + i + "}");
}
xe.SetValue(value);
xmlDoc.Add(xe);
xmlDoc.Save(xmlPath);
}
private static void WriteComment(XElement xmlDoc, string value)
{
XComment xComment = new XComment(value);
xmlDoc.Add(xComment);
xmlDoc.Save(xmlPath);
}
}
8、内容取出来后XML中大概是这个样子的。转换之前要把Excel表关闭,不然后报错“IOException: Sharing violation on path E:\Test.xlsx”
Test1
Test2
Test3 {0} Test3
有个这样的工具太方便了,取翻译几秒钟就搞定了。当然前提是文档是按约定的格式做的,比如工作表的顺序,内容的格式等,如果格式不对的话,取出来的内容就会混乱了。