quick-x-05.客户端框架之excel表转换解析
解析约定
Excel导出的Txt的文本,约定
- 第一行为字段类型,
- 第二行为字段名称,
- 第三行以后’#’开头为字段注释,非’#’开头则为字段内容
- 最终excel导出Txt文本如下图:
- excel一键导出编码为ANSI的Txt(下面需要利用python将txt文本解析成客户端用lua文件,所以要注意编码) 点击这里
定制客户端所需的配置表文件、配表对应的实体文件
lua配置表,和lua配置表对应的实体文件。都是通过python解析对应的txt文本来获得。
客户端配表文件
期望的客户端配置表格式
我期望的txt转成的lua配置表格式为
local 配置表表名 = {
{"第一行第一个字段的值", "第一行第二个字段的值", "第一行第三个字段的值"...},
{"第一行第一个字段的值", "第一行第二个字段的值", "第一行第三个字段的值"...}
}
return 配置表表名
在开发过程中,可能会遇到某个字段是多个值,所以我们在excel中填充字段时,遇到字段为多个值时,用’|’来分隔开。
- 如下图
最终的txt转成的lua配置表格式应该是这样的
local 配置表表名 = {
{"第1行第1个字段的值", {"第1行第2个字段的1值","第1行第2个字段的2值"...}, "第1行第3个字段的值"...},
{"第2行第1个字段的值", "第2行第2个字段的值", "第2行第3个字段的值"...}
}
return 配置表表名
利用python将txt转成期望的lua配表格式
操作记录:
- 按行读取文本数据,将每一行数据存在数组里(这里需要注意编码类型)
- 剔除变量类型行、变量名称行、变量注释行
- 将内容数据存到数组中,递归拼接字符写入到lua文件中
- 部分示例代码(为了能减少篇幅,将一些容错删减掉了):
lines = [] # 转载该所有行
fp = open(filePath, "r")
line = fp.readline()
while len(line) != 0:
line = line.replace('\r','')
line = line.replace('\n','')
lines.append(line) # 这里需要注意编码
line = fp.readline()
fp.close()
# 排除变量类型行数据
column = len(lines[0]) 获取多少的个字段
del lines[0]
# 排除变量名字行数据
del lines[0]
# 排除注释行(注释行行数不定)
noteList = []
while len(lines) >0 and lines[0].startswith('#'):
del lines[0]
# 将每一行数据存放到数组里面,若单元格为数组,则嵌套数组如:
# [
# ["第1行第1个字段","第2行第2个字段"],
# ["第1行第1个字段",["第2行第2个字段第1个值","第2行第2个字段第2个值"]],
# ]
contentList = []
for contentLine in lines: # 遍历行数据
contentLineList = contentLine.split('\t') # 分割字符
contentLineList = contentLineList[0:column]
if contentLineList[0] != '': # 首元素不能为空
tmpContentLineList = []
for oneTab in contentLineList:
if oneTab.find('|') != -1: # 单元格为table
oneTabList = oneTab.split('|')
if oneTabList[0] == '':
del oneTabList[0]
tmpContentLineList.append(oneTabList)
else:
tmpContentLineList.append(oneTab)
contentList.append(tmpContentLineList)
# 将list转成lua table
def listToLuaTabStr( oneList ):
retStr = '{'
for oneTab in oneList:
if isinstance(oneTab,(list)) == True: # 如果是list就递归拼接
retStr += listToLuaTabStr(oneTab)+','
else:
oneTab = oneTab.replace('\"','\\\"')
retStr += '\"' + oneTab + '\",'
retStr += '}'
return retStr
luaFp = codecs.open("路径+导出的lua文件名.lua","w","utf-8")
luaFp.write('local ' + "表名" + ' = \n{\n')
for oneList in contentList:
writeStr = listToLuaTabStr(oneList)
luaFp.write('\t'+writeStr+',\n')
luaFp.write('\n}\nreturn ' + dataName)
luaFp.close()
- 最终导出的lua文件如下图:
客户端lua配表对应的实体lua文件
期望的客户端lua配置表对应的实体lua文件格式为
我期望的lua表对应的lua实体文件格式为
配置表表名 + "Lib" = class(配置表表名 + "Lib")
local 数据列表 = nil -- 数据队列
local 文件名称 = nil -- 文件名(不含扩展名)
-- 构造函数
function 配置表表名 + "Lib":ctor(list, fileName)
self.字段名称1 = list[1] -- 字段注释
self.字段名称2 = list[2] -- 字段注释
self.key = self.字段名称1 -- dataList存储key,key作为表索引
if self.custom_ctor ~= nil then -- 自定义 ctor 部分
self:custom_ctor()
end
end
-- 注册函数
function 配置表表名 + "Lib".processData(dir, file)
数据列表,文件名称 = LibraryManager.load(dir, file, 配置表表名 + "Lib")
end
-- 根据id获取条目
function 配置表表名 + "Lib".getDataById(id)
return 数据列表[id]
end
-- 获取所有条目
function 配置表表名 + "Lib".getDataList()
return 数据列表
end
----------------------------BEGIN-------------------------------
-- 给用户留自定义部分。下次生产解析文件,不会被覆盖
-- TODO
-----------------------------END--------------------------------
return 配置表表名 + Lib
- 首先要保存用户自定义部分,保证下次字段修改,再次生成lua解析配表不会将用户自定义部分给覆盖掉。
- 其次提供几个公用函数,每个lua表对应的实体都会有的getDataById函数和getDataList函数。
- 最后就是字段名称和字段值、字段索引的行对应
- LibraryManager中对lua配表进行实体对象创建操作,下面会记录到
利用python写入对应的数据实体类
操作记录:
- 实体文件存在则先保存用户自定义部分内容,记录到数组中
- 写入变量名、变量值、变量注释。
- 将之前保存的用户自定义部分内容,从新写到实体文件中
- 部分示例代码(为了能减少篇幅,将一些容错删减掉了):
# 写程序lib文件
def writeLuaLibFile(dataName,typeList,nameList,noteList):
libClassName = dataName+'Lib'
libFilePath = os.path.join(libDir,dataName+'Lib.lua')
# 如果文件存在,需要保存用户自定义的内容
extLines = []
isExtLine = False
if os.path.exists(libFilePath):
readFp = codecs.open(libFilePath,"r","utf-8")
oneLine = readFp.readline()
if len(oneLine) != 0:
# 去掉文件头说明
oneLine = readFp.readline()
while len(oneLine) != 0:
if isExtLine == True:
extLines.append(oneLine)
if oneLine.find('START EXT END') != -1:
isExtLine = False
break
else:
if oneLine.find('START EXT BEGIN') != -1:
extLines.append(oneLine)
isExtLine = True
oneLine = readFp.readline()
readFp.close()
libFp = codecs.open(libFilePath,"w","utf-8")
libFp.write(libClassName + ' = class(\"' + libClassName + '\")\n\n')
libFp.write('local dataList = nil -- 数据队列\n')
libFp.write('local fileName = nil -- 文件名(不含扩展名)\n\n')
libFp.write('-- 构造函数\n')
libFp.write('function %s:ctor(list, fileName)\n' % libClassName)
num = len(nameList)
for i in range(0,num):
oneProperty = nameList[i]
leq = '\tself.%s' % oneProperty
libFp.write(leq.ljust(30)+'= ') # 写字段
if typeList[i] == 'INT':
req = 'tonumber(list[%d])' % (i+1)
libFp.write(req.ljust(25)+'-- ') # 写值
else:
req = 'list[%d]' % (i+1)
libFp.write(req.ljust(25)+'-- ')
# 写注释
for note in noteList:
libFp.write(note[i] + ' ')
libFp.write('\n')
libFp.write('\n')
leq = '\tself.key'
sKey = 'self.%s' % nameList[0]
libFp.write(leq.ljust(30)+'= ' + sKey.ljust(25) + '-- dataList存储key\n')
libFp.write('\t-- 自定义 ctor 部分\n')
libFp.write('\tif self.custom_ctor ~= nil then\n')
libFp.write('\t\tself:custom_ctor()\n')
libFp.write('\tend\n')
libFp.write('end\n\n')
写入processData方法...
#processData()
写入getDataById方法...
#getDataById()
写入getDataList方法
#getDataById()
# 自定义扩展区间
if len(extLines) == 0: # 原来的文件中不存在用户自定义代码
libFp.write('----------------------------BEGIN-------------------------------\n')
libFp.write('-- 给用户留自定义部分。下次生产解析文件,不会被覆盖\n\n')
libFp.write('-----------------------------END--------------------------------\n\n')
else: # 存在则将之前的用户自定义写入到新文件中
for extLine in extLines:
libFp.write(extLine)
libFp.write('return ' + libClassName)
libFp.close()
- 最终导出的配表对应的实体类文件如下图:
LibraryManager文件中加载配表、创建配表模型对象
- 示例解析代码:
LibraryManager = {}
--加载配表
--@param dir lua配表文件路径
--@param file lua配表文件名
--@param libClass 映射Class
--@return 返回分离数据table(table)、返回对应加载的文件名
function LibraryManager.load(dir, file, libClass)
local tableData = require(dir .. file)
local fileName = string.sub(file, 1, #file-#".lua") -- 截取文件名剔除.lua后缀
local lib = {}
local cls = nil
for i = 1, #tableData do
cls = libClass.new(tableData[i]) -- 每一行数据填充一个实体对象
if lib[cls.key] ~= nil then
SYSLog("##########Error:Key is not only, fileName:" .. fileName .. ", id:" .. tostring(cls.key))
end
lib[cls.key] = cls -- key做表索引
end
return lib, fileName
end
-- 加载所有配表
function LibraryManager.init()
SYSLog("LibraryManager init start")
ConfigLocalTextDataLib.processData(Config.CONFIG_PATH, "ConfigLocalTextData.lua") -- 多语言表
-- TODO... other table data
SYSLog("LibraryManager init end")
end
return LibraryManager
- LibraryManager.init()函数在进入游戏调用,来加载所有配置表,转成对象表模式
- LibraryManager.load()函数将每一行数据创建一个对应的实体对象,并返回一个实体表
- 最终效果图
遇到的坑
- python在读取文件时(
unicode(line, 'utf-8').decode('utf-8')
),需要十分注意编码格式。excel中导出Txt一般为Unicode编码,这种编码在python中读取会报错。在excel中导出utf8操作实在是繁琐,所以利用vbs来一键导出ANSI编码的Txt