├── .gitmodules ├── README.md ├── excel └── Dungeon.xls ├── excel2lua_final.py ├── excel2lua_mod.py ├── excel_cmd.txt ├── get_xls_changelist.sh └── rapid_do.sh /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "xlrd"] 2 | path = xlrd 3 | url = git@github.com:python-excel/xlrd.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## excel2lua说明 2 | 3 | --- 4 | 5 | ### 使用依赖 6 | * python2.7+ 7 | * python一个库xlrd, 已经作为git submodule引用了。 8 | 9 | ``` 10 | cd xlrd 11 | git submodule init 12 | git submodule update 13 | ``` 14 | 15 | ### 作用 16 | * 支持xls/xlsx的导出,视xlrd版本而定 17 | * 对于excel的结构定义,见示例中的excel/Dungeon.xls 18 | 19 | ### 思路 20 | 21 | ### 核心部分: 22 | * excel2lua_mod.py 23 | `用于将某个excel中某个sheet转换为lua的配置table` 24 | 25 | * excel2lua_final.py 26 | `调用excel2lua_mod.py,传递一些参数。此模块可单独使用。` 27 | 28 | ### 外围部分 29 | * get_xls_changelist.sh 通过svn的对比工具,查找出有变化的xls, 供增量生成配置 30 | * excel_cmd.sh 这个是用于查找某个xls文件它的生成配置的命令 31 | * rapid_do.sh/do_all.sh 主(控)逻辑脚本 32 | 33 | 34 | ### 后记 35 | * 因为当时写此脚本为临时需求,没考虑太多的结构方面东西,代码quick&dirty 36 | -------------------------------------------------------------------------------- /excel/Dungeon.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin1sMe/excel2lua/4e1206d5bb0e275bd1798b788d343e5c417f9e0b/excel/Dungeon.xls -------------------------------------------------------------------------------- /excel2lua_final.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | #coding=utf-8 3 | ''' 4 | #======================================================================== 5 | # FileName: excel2lua_final.py 6 | # Author: kevinlin 7 | # Desc: 调用excel2lua_mod.py来生成配置 8 | # Email: linjiang1205 AT qq.com 9 | # LastChange: 2014-12-20 16:55:32 10 | #======================================================================== 11 | ''' 12 | import os 13 | import sys 14 | import commands 15 | from excel2lua_mod import * 16 | import getopt 17 | 18 | def usage(): 19 | print ''' 20 | Usage: %s excel_file sheet_name ["repeated=[0|1]", "subkey=NUM" , "output=[c|s|cs|sc]"] 21 | 22 | excel_file: the source file of config 23 | sheet_name: the sheet you want convent to lua 24 | repeated: is key repeated?? 25 | 0 means no repeated key inside 26 | 1 means the key maybe repeated, then parse them as one table 27 | subkey: table nested. point out which column is the sub key 28 | output: [c|s|cs|sc] client or svr or both?? 29 | c means client, has some different with s 30 | s means server, has some different with c 31 | 32 | ''' %(sys.argv[0]) 33 | 34 | 35 | if __name__ == '__main__' : 36 | if len(sys.argv) < 3 : 37 | usage() 38 | sys.exit(-1) 39 | 40 | xls_file_name = sys.argv[1] 41 | #读取sheet名,转为大写字母 42 | sheet_name = sys.argv[2].upper(); 43 | #是否有重复的key,为0时表示key不重复。为1时有重复key,将对某个key产生多一层的table 44 | have_repeated_key = 0 45 | #当有重复的key时,这里指定index从0开始,还是以第二列的数值为index(默认从0开始。参考Dungen.xls) 46 | sub_key_col = 0 47 | #输出目标、client/svr 48 | output="" 49 | 50 | try: 51 | opt, args = getopt.getopt(sys.argv[3:], "hr:s:o:", ["help", "repeated=", "subkey=", "output="]) 52 | except getopt.GetoptError, err: 53 | print "err:",(err) 54 | usage() 55 | sys.exit(-1) 56 | 57 | for op, value in opt: 58 | if op == "-h" : 59 | usage() 60 | elif op == "-r" or op == "--repeated": 61 | have_repeated_key = int(value) 62 | elif op == "-s" or op == "--subkey": 63 | sub_key_col = int(value) 64 | elif op == "-o" or op == "--output": 65 | output = value 66 | 67 | logging.debug("have_repeated_key:%d|sub_key_col:%d|output:%s"%(have_repeated_key, sub_key_col, output)) 68 | 69 | outputstr = {"c": "client", "s":"svr"} 70 | 71 | for i in output: 72 | if not outputstr.has_key(i): 73 | usage() 74 | sys.exit(-2) 75 | 76 | parser = CardInterpreter(xls_file_name,sheet_name, have_repeated_key, sub_key_col) 77 | parser.Interpreter(outputstr[i]) 78 | file_name = GetFileName(outputstr[i], sheet_name) 79 | sheet_name_lower = sheet_name.lower(); 80 | context = OutputFileHeader(xls_file_name, file_name, sheet_name_lower, False) 81 | context += parser.GetResult() 82 | Write2File(file_name,context) 83 | 84 | # 试运行产生的lua脚本看是否会报错 85 | TestLuaFile(file_name) 86 | 87 | -------------------------------------------------------------------------------- /excel2lua_mod.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | #coding=utf-8 3 | ''' 4 | #======================================================================== 5 | # FileName: excel2lua_mod.py 6 | # Author: kevinlin 7 | # History: 8 | 9 | 2013年8月15日 18:10:58 创建文件 10 | 11 | 2013年8月15日 22:27:06 添加了对默认值的支持。当为空时,使用默认值。 12 | 13 | 2013年8月16日 11:50:21 添加带级别的标准输出? 和打印日志类似。如设置级别高时,只打印特别重要的日志,当级别低时,一些调试的日志会打印出来 当脚本解析出问题时,调整日志级别以便更好的定位问题 14 | 15 | 2013年8月21日 22:21:20 将通过这个导出的配置统一在模块config下。其实不用模块更好 。直接全局可见? 16 | 17 | 2013年8月28日 12:48:32 添加对表格中有重复的key的解析。在使用脚本时通过参数指定是否有此行为 18 | 19 | 2013年9月13日 16:13:15 将这个解析模块独立出来。main拆在别的文件中实现 20 | 21 | 2013年9月5日 19:37:25 添加支持subkey.当key有重复时,可以指定是否有sub_key, 如果设置sub_key_col为非0.则认为那行是sub key的值。 22 | 23 | 2013年11月8日 13:29:58 将table中的索引从1开始(之前从0,不太符合lua的数组规则) 24 | 25 | 2013年11月14日 16:21:36 支持表格的key为string。生成的table形式如 ["宋江"] = {70000100, 70000101} 26 | 27 | 2013年12月2日 17:10:15 对于array数组解析,自动去除多余的分隔符(在文本末尾的分隔符,如123;355;这里的最后那个分号;) 28 | 29 | 2013年12月19日 17:07:59 添加自行指定分隔符功能,在array后添加=, 或者=|这些,指定分隔符为,或者| 30 | 31 | 2014年1月7日 15:10:51 修改digit array/string array中当value为空时,生成失败的问题 32 | 33 | 2014年1月8日 20:41:59 修改digit类型的值,使不会生成浮点数值。我们不会有浮点数。全部转换为整数 34 | 修改对于array类型,生成时不使用默认值,不然会有{0}这种非预期的输出 35 | 36 | 2014年1月21日 16:47:41 这里将配置生成的空项去除, 使用defaultValue = "nil" 作为是否置空的标志。 37 | 38 | 2014年1月23日 00:05:17 修复上面修改造成的bug.有些空的struct地方需要跳过生成 39 | 40 | 2014年6月19日 13:15:47 将客户端生成的配置,去掉date那一行。否则说会影响生成配置的差异包。。 41 | 42 | 2014年12月20日 14:03:01 简单重构此模块,将代码写得易懂些 43 | 44 | # TODO : 45 | 1. 将表格的定义添加到生成的lua文件中,以注释形式,这样好像更方便观看lua配置 46 | 2. 格式对得不太整齐。并且有些不必要的分号和空格考虑去掉 47 | 3. 将struct结构内当没有填东西时,忽略掉不生成为空的 48 | 49 | # LastChange: 2015-1-10 50 | #======================================================================== 51 | ''' 52 | import os 53 | import sys 54 | from datetime import * 55 | import time 56 | import types 57 | import commands 58 | import re 59 | import logging 60 | 61 | reload(sys) 62 | sys.setdefaultencoding( "utf-8" ) 63 | 64 | ''' 65 | 有以下几种日志级别供选择,如果需要修改请设置LOG_LEVEL 66 | logging.debug("debug") 67 | logging.info("info") 68 | logging.warning("warning") 69 | logging.error("error") 70 | logging.critical("critical") 71 | ''' 72 | try: 73 | import xlrd 74 | except: 75 | #print "not find xlrd in python sit-packages, import xlrd from local directory" 76 | libPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'xlrd') 77 | sys.path.append( libPath ) 78 | import xlrd 79 | 80 | 81 | #LOG_LEVEL = logging.DEBUG 82 | LOG_LEVEL = logging.WARNING 83 | 84 | #2013年8月16日 11:40:34 之前去掉了日志功能,现在重新添加上。虽然反复了,但目标是不一样的。 85 | #现在是为了控制在标准输出中的内容和兼顾调试 86 | logging.basicConfig(level=LOG_LEVEL, format="[%(levelname)s]%(message)s") 87 | 88 | class HeaderItem: 89 | pass 90 | 91 | # 一个通用的表格输出函数,根据表格类型进行判断 92 | class CardInterpreter: 93 | """通过excel配置生成配置的lua定义文件""" 94 | def __init__(self, xls_file_path, sheet_name, have_repeated_key, sub_key_col): 95 | self._xls_file_path = xls_file_path 96 | self._sheet_name = sheet_name 97 | self._sheet = None; 98 | 99 | logging.debug('xls_file_path:%s, sheet_name:%s'%(self._xls_file_path, self._sheet_name)) 100 | 101 | #是否有重复的key 102 | self._have_repeated_key = have_repeated_key 103 | #当有重复key时,将以表格中第几列_sub_key_col列为sub key。如果为0则从0开始 104 | self._sub_key_col = sub_key_col 105 | 106 | #初始化 107 | #定义了表格的头部;那些结构。。用于后续数据的读取和解析 108 | self._header = [] 109 | 110 | #产生的lua结果集 111 | self._context = "" 112 | 113 | #打开表格读取基本信息 114 | try : 115 | self._workbook = xlrd.open_workbook(self._xls_file_path) 116 | except BaseException, e : 117 | logging.error("open xls file(%s) failed!" %(self._xls_file_path)) 118 | raise 119 | try: 120 | self._sheet = self._workbook.sheet_by_name(self._sheet_name) 121 | except Exception, e: 122 | logging.error("open sheet(%s) failed!"%(self._sheet_name)) 123 | raise 124 | # 行数和列数 125 | self._row_count = self._sheet.nrows 126 | self._col_count = self._sheet.ncols 127 | logging.debug("sheet info|name:%s, row:%d col:%d " 128 | % (self._sheet_name, self._row_count, self._col_count)) 129 | 130 | 131 | def __del__(self): 132 | ''' 133 | 在析构时关闭日志文件 134 | 135 | ''' 136 | 137 | def Interpreter(self, svr_or_client) : 138 | """对外的接口, 解析xlsx中并生成配置文件""" 139 | logging.debug("==begin CardInterpreter[%s] in %s|row:%d,col:%d==", 140 | self._sheet_name, self._xls_file_path, self._row_count, self._col_count) 141 | 142 | #清空旧东西以便重入, 因为要生成客户端和服务器的配置 143 | self._header = [] 144 | self._context = "" 145 | 146 | #解析配置 147 | self._ParseSheet(svr_or_client); 148 | 149 | def _Conv2IntStr(self, s): 150 | """将输入转为int的字符串,输入可能是str也可能是float""" 151 | logging.debug("_Conv2IntStr:%s,%s",s,str(type(s))) 152 | if str(type(s)) == "": 153 | if s != "": 154 | if s == "nil": 155 | return "" 156 | if -1 == s.find('.'): 157 | return str(int(str(s))) 158 | else: 159 | #2014年1月8日 20:38:33 咱不生成float的东东 160 | return str(int(float(str(s)))) 161 | else: 162 | return '0' 163 | elif str(type(s)) == "": 164 | #TODO 对数字进行一个校验() 165 | return s 166 | elif str(type(s)) == "": 167 | return str(s) 168 | elif str(type(s)) == "": 169 | return str(int(s)) 170 | else: 171 | logging.warning("type(s) = %s", str(type(s))) 172 | raise "conv2intstr err",s 173 | 174 | 175 | 176 | def _GetColHeader(self, col): 177 | """获得某一列的头部信息,一般为前面的3~4行 178 | 返回:bool, (col, colName, outType, valueType, colDesc) 179 | 如果是合法的头部,返回bool为true,后面跟的元组有意义。 否则为false,其后元组为空 180 | 181 | col 为列号 182 | colName为这一列的命名,英文,用于定义某项。如GoodsID 183 | outType为这一列用于输出的定义,指明是用于客户端,还是服务器或者都有用,取值B|S|C 184 | valueType为这一列的值的类型。指明如何解析这一列。[digit|string] [ |array] 或者是 struct 185 | colDesc是一个备注的描述 186 | """ 187 | logging.debug("_GetColHeader:col=%d,total_col=%d"%(col,self._col_count)) 188 | #读取colName,这个可以默认在第一行,读不到或者为空认为此列无效 189 | if col >= self._col_count: 190 | return False,() 191 | 192 | #NOTICE: 这里有个强约定,要求xls的前三行是按要求写的。 193 | #第一行为name,第二行为out type, 第三行为value type 194 | 195 | #2013年8月15日 22:06:10 让表格支持默认值 例:Name="kevin",当Name列为空时,使用默认值kevin 196 | name = str(self._sheet.cell(0, col).value).strip().split('=') 197 | 198 | colName = name[0].strip() 199 | if colName == "": 200 | return False,() 201 | 202 | logging.debug("name: %s len:%d"%(name,len(name))) 203 | 204 | #读取outType,这个可以默认在第二行;要根据valueType判断是否这个合法 205 | outType = str(self._sheet.cell(1, col).value).strip() 206 | 207 | #读取valueType, 第三行,只能是定义的一些字符串 208 | valueType = str(self._sheet.cell(2, col).value).strip() 209 | logging.debug("valueType:%s"%(valueType)) 210 | 211 | valueTypeLen = len(valueType.split('=')) 212 | valueTypeExt = "" 213 | if valueTypeLen > 1 : 214 | valueTypeExt = valueType.split('=')[1] 215 | valueType = valueType.split('=')[0] 216 | 217 | #默认值 218 | defaultValue = 0 219 | 220 | if len(name) > 1: 221 | if valueType.split()[0] == "digit": 222 | defaultValue = self._Conv2IntStr(name[1].strip()) 223 | else: 224 | defaultValue = name[1].strip() 225 | else: 226 | #对于array形式,不设置其默认值了,不然会产生{0}这样的非预期的table 227 | if len(valueType) > 0 and valueType.split()[0] == "digit" and len(valueType.split()) <= 1: 228 | defaultValue = 0 229 | #defaultValue = "nil" #取消当没有定义默认值时,就不使用默认值,直接不生成此字段,以降低lua table的内存消耗 230 | else: 231 | #defaultValue = "nil" 232 | defaultValue = "" 233 | 234 | 235 | #读取描述==没啥用。读进来方便看看而已 236 | colDesc = str(self._sheet.cell(3, col).value).strip() 237 | 238 | #校验一下合法性 239 | vaild_types = [u"string", u"digit", u"time", u"string array", u"digit array", u"struct"] 240 | if colName == "": 241 | logging.debug("colName is empty??") 242 | return False,() 243 | 244 | if not valueType in vaild_types: 245 | logging.debug("valueType(%s) is invaild??", valueType) 246 | return False,() 247 | 248 | 249 | logging.debug("Succ: col:%d\tcolName:%s\toutType:%s\tvalueType:%s\tcolDesc:%s\tdefaultValue:%s" 250 | %(col,colName,outType,valueType,colDesc,defaultValue)) 251 | 252 | return True,(col, colName, outType, valueType, colDesc, defaultValue, valueTypeExt) 253 | 254 | def _ParseHeaderInfo(self, svr_or_client): 255 | """ 256 | 从表格中读取描述信息。以此定义出这个表格结构。其中使用了python的一些动态特性 257 | """ 258 | col = 0 259 | while col < self._col_count: 260 | ret,header = self._GetColHeader(col) 261 | if not ret: 262 | logging.debug('jump..unkown col') 263 | col += 1 264 | continue 265 | 266 | #创建对象。方便后面访问 267 | header_item = HeaderItem() 268 | setattr(header_item, "col", header[0]) 269 | setattr(header_item, "colName", header[1]) 270 | setattr(header_item, "outType", header[2]) 271 | setattr(header_item, "valueType", header[3]) 272 | setattr(header_item, "colDesc", header[4]) 273 | setattr(header_item, "defaultValue", header[5]) 274 | setattr(header_item, "valueTypeExt", header[6]) 275 | 276 | #跳过svr/client不匹配的列 277 | logging.debug("svr_or_client:%s outType:%s"%(svr_or_client,header_item.outType)) 278 | if svr_or_client == "svr" and header_item.outType == "C": 279 | logging.debug("this is client config, server no need") 280 | col += 1 281 | continue 282 | elif svr_or_client == "client" and header_item.outType == "S": 283 | logging.debug("this is server config, client no need") 284 | col += 1 285 | continue 286 | 287 | self._header.append(header_item) 288 | 289 | if header_item.valueType == "struct": 290 | struct_items = [] 291 | struct_item_idx = 0 292 | struct_idx = 0 293 | 294 | #如果是struct类型,才创建这个属性subNode 295 | setattr(header_item, "subNode", []) 296 | 297 | idx_max = int(float(header[2])) 298 | logging.debug('idx_max:%d'%(idx_max)) 299 | key_name_list = [] 300 | while struct_idx < idx_max: 301 | col += 1 302 | ret,header = self._GetColHeader(col) 303 | if not ret: 304 | logging.debug('jump..unkown col') 305 | col += 1 306 | continue 307 | item = HeaderItem() 308 | setattr(item, "col", header[0]) 309 | setattr(item, "colName", header[1]) 310 | setattr(item, "outType", header[2]) 311 | setattr(item, "valueType", header[3]) 312 | setattr(item, "colDesc", header[4]) 313 | setattr(item, "defaultValue", header[5]) 314 | setattr(item, "valueTypeExt", header[6]) 315 | 316 | #添加到key_name_list中作为辨别这个struct的标识 317 | key_name_list.append(item.colName) 318 | 319 | logging.debug("struct_idx:%d col:%d"%(struct_idx,col)) 320 | #print "item-->", item.__dict__ 321 | 322 | if svr_or_client == "svr" and item.outType == "C": 323 | logging.debug("this is client config, server no need") 324 | pass 325 | elif svr_or_client == "client" and item.outType == "S": 326 | logging.debug("this is server config, client no need") 327 | pass 328 | else: 329 | struct_items.append(item); 330 | 331 | #logging.debug('\t\tcol:',header[0], '\tcolName:', header[1], "\toutType:", header[2], "\tvalueType:", header[3],"\tcolDesc",header[4]) 332 | 333 | #添加到上面的subNode列表中去 334 | #struct_item.subNode.append(item) 335 | struct_idx += 1 336 | 337 | #如果后面还有属于这个结构的,则重置一些参数 338 | if struct_idx >= idx_max:#当已经到最后一个时,检查后续是否符合这个结构定义。如果符合就继续读 339 | struct_item = HeaderItem() 340 | setattr(struct_item, "subNode", struct_items) 341 | setattr(struct_item, "col", header[0]) 342 | setattr(struct_item, "colName", "[%d]"%(struct_item_idx)) 343 | setattr(struct_item, "outType", header[2]) 344 | setattr(struct_item, "valueType", header[3]) 345 | setattr(struct_item, "colDesc", header[4]) 346 | 347 | #logging.debug('struct_item',struct_item) 348 | header_item.subNode.append(struct_item) 349 | 350 | #FIXME 偷懒只检查后面的一项是否和key_name_list[0]吻合就好了,暂时无大碍 351 | ret,header = self._GetColHeader(col+1) 352 | #logging.debug('chech next struct:',header[1],key_name_list) 353 | if ret and header[1] == key_name_list[0]: 354 | struct_idx = 0 #强制将循环的参数设置为0.很暴力吧 355 | key_name_list = [] 356 | struct_items = [] 357 | struct_item_idx += 1 358 | col +=1 359 | 360 | 361 | #print "self._header", self._header 362 | 363 | 364 | def _ParseSheet(self, svr_or_client): 365 | #解析表的头部定义, 这个是作为配置生成的重要根据 366 | self._ParseHeaderInfo(svr_or_client) 367 | 368 | """ 369 | 这个给我写代码时作参考 370 | [101011]={ 371 | [1]={Index=1, IsTeamate=1, Content="洒家大好的心情吃酒,你们这群泼皮好生吵闹", }, 372 | [2]={Index=2, IsTeamate=0, Content="擦,大爷我爱怎样就怎样,哪里由得你这个酒肉花和尚管得", }, 373 | [3]={Index=3, IsTeamate=1, Content="废话…… 放马过来,洒家今天就让你知道花儿为何这样红", }, 374 | }, 375 | """ 376 | 377 | #从表格中读取内容到一个字符串中暂存。。话说字符串最多能存多少东西呢 378 | self._context = self._ReadContextFromSheet(svr_or_client) 379 | 380 | def GetResult(self): 381 | """ 382 | 在_ParseSheet之后,会将产生的lua配置的string放在成员_context中。通过这个来读取 383 | """ 384 | return self._context 385 | 386 | 387 | def _ReadContextFromSheet(self, svr_or_client): 388 | """ 389 | 读取表格中的内容。要基于前面已经解析的HeaderInfo()来 390 | """ 391 | logging.debug("ReadContextFromSheet|%s", svr_or_client) 392 | context_result = "" 393 | #打印最前面的一些信息 394 | context_result += self._sheet_name + " = {\n" 395 | 396 | last_key_id = 0 397 | repeated_key_index = 0 398 | 399 | for row in range(4, self._row_count): #从第4行开始是因为前三行被用来定义表结构了 400 | #把头部定义的第一个合法项认为是key.当这个不存在或者不合法时,此行作废 401 | header_item = self._header[0] 402 | col = header_item.col #col是在header_item对象动态添加的属性 403 | value = "" 404 | if self._sheet.cell(row, col).ctype == xlrd.XL_CELL_TEXT: 405 | value = self._sheet.cell(row, col).value.encode("utf-8") 406 | else:#其它类型都当作数字来处理 407 | value = self._sheet.cell(row, col).value 408 | 409 | if value == "": 410 | logging.debug("this key is null...continue") 411 | continue 412 | 413 | #识别key的类型,是integer还是string 414 | key_is_integer = True 415 | try: 416 | this_key = self._Conv2IntStr(value) 417 | key_is_integer = True 418 | except: 419 | this_key = "\"%s\""%(value) 420 | key_is_integer = False 421 | 422 | 423 | #将key对应的头部输出来 424 | row_str_for_output = "" 425 | if self._have_repeated_key == 0: 426 | row_str_for_output += '\t[' + this_key + '] = {' 427 | else: 428 | if this_key != last_key_id: #有key的切换,需要把key输出来 429 | if last_key_id != 0: 430 | row_str_for_output += '\n\t\t},\n' 431 | repeated_key_index = 0 432 | row_str_for_output += '\t[' + this_key + '] = {' 433 | 434 | last_key_id = this_key 435 | 436 | 437 | 438 | #如果定义这个表有重复的key,则产生的表多一层。加个[0]={} [1]={} 等 439 | if self._have_repeated_key == 1: 440 | repeated_key_inside = 0 441 | if self._sub_key_col == 0: 442 | repeated_key_inside = repeated_key_index + 1 443 | else: 444 | sub_key_value = "" 445 | if self._sheet.cell(row, self._sub_key_col).ctype == xlrd.XL_CELL_TEXT: 446 | sub_key_value = self._sheet.cell(row, self._sub_key_col).value.encode("utf-8") 447 | else:#其它类型都当作数字来处理 448 | sub_key_value = self._sheet.cell(row, self._sub_key_col).value 449 | repeated_key_inside = int(sub_key_value) 450 | 451 | logging.debug("self._sub_key_col:%d|repeated_key_inside:%d"%(self._sub_key_col, repeated_key_inside)) 452 | #2013年11月8日 13:33:15 使重复key时index从1开始,之前是从0 453 | row_str_for_output += '\n\t\t[%d]={'%(repeated_key_inside) 454 | repeated_key_index += 1 455 | 456 | #如果key合法。尝试来解析这一行 457 | for header_item in self._header[1:]: 458 | col = header_item.col 459 | value = self._sheet.cell(row, col).value 460 | 461 | if header_item.valueType == "struct": #解析结构体,使用递归吗? 462 | row_str_for_output += "\n\t\t" + header_item.colName + ' = {\n' 463 | struct_idx = 1 464 | #logging.debug('in parse-->header_item',header_item.__dict__) 465 | for item in header_item.subNode: 466 | #row_str_for_output += "\t\t\t[%d] = {"%(struct_idx) 467 | row_str_in_side = "" 468 | row_str_in_side += "\t\t\t[%d] = {"%(struct_idx) 469 | #logging.debug("\nitem",item.__dict__) 470 | all_sub_item= "" 471 | for in_item in item.subNode: 472 | #logging.debug("\n\nin_item",in_item.__dict__) 473 | #row_str_for_output += self._GetItemString(in_item, row) 474 | all_sub_item += self._GetItemString(in_item, row, svr_or_client) 475 | struct_idx += 1 476 | #row_str_for_output += "},\n" 477 | row_str_in_side += all_sub_item 478 | row_str_in_side += "},\n" 479 | 480 | if all_sub_item == "": 481 | row_str_in_side = "" 482 | 483 | row_str_for_output += row_str_in_side 484 | 485 | row_str_for_output += '\t\t},\n\t\t' 486 | else: 487 | row_str_for_output += self._GetItemString(header_item, row, svr_or_client) 488 | 489 | #row_str_for_output += '\n\t},' 490 | row_str_for_output += '},' 491 | 492 | 493 | #对于长度比较短的行,去除一些多余的符号,使生成格式更美观 494 | #if len(row_str_for_output) < 120: 495 | #row_str_for_output = row_str_for_output.replace('\n','') 496 | #row_str_for_output = row_str_for_output.replace('\t','') 497 | #row_str_for_output = '\t' + row_str_for_output 498 | 499 | #if self._have_repeated_key == 0: 500 | #row_str_for_output += '\n' 501 | 502 | row_str_for_output += '\n' 503 | 504 | context_result += row_str_for_output 505 | logging.info(row_str_for_output) 506 | 507 | #在最后的最后, 需要多加一个}, 508 | if self._have_repeated_key == 1: 509 | context_result += '\n\t\t},\n' 510 | 511 | 512 | context_result += "};" 513 | logging.info(context_result) 514 | return context_result 515 | 516 | 517 | def _GetItemString(self, header_item, row, svr_or_client): 518 | logging.debug('%s :_GetItemString <--%d %d'%(svr_or_client, row, header_item.col)) 519 | return_str = "" 520 | value = self._sheet.cell(row, header_item.col).value 521 | if value == "": #当为空时,使用默认值 522 | #if header_item.defaultValue == "nil": 523 | #return "" 524 | if svr_or_client == "client" : 525 | return "" 526 | value = header_item.defaultValue 527 | logging.debug('value is "" use default value:%s'%(str(value))) 528 | 529 | if header_item.valueType == "digit": #解析数字 530 | return_str += header_item.colName + '=' + self._Conv2IntStr(value) + ', ' 531 | elif header_item.valueType == "string": #解析字符串 532 | strip_new_line = unicode(value).replace("\n","") 533 | return_str += header_item.colName + '= "' + strip_new_line + '", ' 534 | elif header_item.valueType == "time": 535 | if str(value) == "": 536 | return_str += header_item.colName + '=0, ' 537 | else: 538 | return_str += header_item.colName + '=' + str(int(time.mktime(time.strptime(str(value), "%Y-%m-%d %X")))) + ', ' 539 | elif header_item.valueType == "digit array" or header_item.valueType == "string array": #数字数组 540 | return_str += header_item.colName + '= {' 541 | #在数字数组中,一般内容是1;2;3 或者 1,2,3,4 或者 1|2|3|4 即有这三种分隔符 542 | #FIXME 本来想做智能感知,用于判断到底用哪个分隔符好。目前暂时认为这几种都是分隔符吧,没大碍 543 | logging.debug('array value: %s'%(str(value))) 544 | for_split_str = str(value) 545 | if len(for_split_str) < 1: 546 | return header_item.colName + " = {}, " 547 | split_char = ';,|:' 548 | #当有定义分隔符时,以这里定义的为准 549 | if header_item.valueTypeExt != "": 550 | split_char = header_item.valueTypeExt 551 | 552 | if split_char.find(for_split_str[-1]) != -1: #最后一个字符是分隔符时,去掉它 553 | for_split_str = for_split_str[:-1] 554 | logging.debug('for_split_str: %s'%(for_split_str)) 555 | 556 | split_value = re.split('[' + split_char + ']',str(for_split_str)) 557 | idx = 0 558 | for item in split_value: 559 | if header_item.valueType == "digit array": 560 | logging.debug('digit array value item: %s'%(item)) 561 | #return_str += '[%d] = %s, '%(idx, self._Conv2IntStr(item)) 562 | return_str += '%s, '%(self._Conv2IntStr(item)) 563 | elif header_item.valueType == "string array": 564 | logging.debug('string array value item: %s'%(item)) 565 | #return_str += '[%d] = "%s",'%(idx, unicode(item)) 566 | return_str += '"%s",'%(unicode(item)) 567 | else: 568 | raise "header_item.valueType:%s err"%(header_item.valueType) 569 | 570 | idx += 1 571 | 572 | 573 | return_str += '}, ' 574 | logging.debug('_GetItemString-->%s'%(return_str)) 575 | return return_str 576 | 577 | def OutputFileHeader(xls_file_name, output_file_name, sheet_name, is_client): 578 | """生成lua文件的描述信息""" 579 | 580 | file_name = output_file_name 581 | output = """--[[ 582 | 583 | @file: %s 584 | @source xls: %s 585 | @sheet name: %s 586 | @brief: this file was create by tools, DO NOT modify it! 587 | @author: kevin 588 | 589 | ]]-- 590 | """; 591 | 592 | output_str = output%(file_name, xls_file_name, sheet_name.upper()) 593 | 594 | output_str += "\nmodule(\"config\", package.seeall)\n\n\n" 595 | 596 | #return output%(file_name, xls_file_name, sheet_name.upper(), mt) 597 | return output_str 598 | 599 | def Write2File(file_name,context) : 600 | """输出到文件""" 601 | open_lua_file = "output/" + file_name 602 | lua_file = open(open_lua_file, "w+") 603 | lua_file.write(context) 604 | lua_file.close() 605 | 606 | def GetFileName(svr_or_client, sheet_name): 607 | if svr_or_client.strip().upper() == "SVR": 608 | file_name = "config_svr_" + sheet_name.strip().lower() + ".lua" 609 | else: 610 | file_name = "config_client_" + sheet_name.strip().lower() + ".lua" 611 | return file_name 612 | 613 | 614 | #def verify(num): 615 | # return ('float', 'int')[round(float(num)) == float(num)] 616 | 617 | def TestLuaFile(filename): 618 | command = "lua output/" + filename 619 | status, output = commands.getstatusoutput(command) 620 | if (status != 0) : 621 | logging.debug("\x1B[0;31;40m WARNING: \e[1;35m Test " + filename + "\t--> FAILED!" + str(status) + output + "\x1B[0m") 622 | print "\x1B[0;31;40m[ERROR]: Test " + filename + " --> FAILED!\x1B[0m" 623 | print "\x1B[0;35;40m\t" + str(status) + output + "\x1B[0m" 624 | #sys.exit(-1) 625 | else : 626 | print "\x1B[0;32;40m[SUCCESS]: Test " + filename + " --> OK\x1B[0m" 627 | 628 | 629 | -------------------------------------------------------------------------------- /excel_cmd.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin1sMe/excel2lua/4e1206d5bb0e275bd1798b788d343e5c417f9e0b/excel_cmd.txt -------------------------------------------------------------------------------- /get_xls_changelist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #======================================================================== 3 | # FileName: get_xls_changelist.sh 4 | # Author: kevinlin 5 | # Desc: 获得增量要更新的配置列表(通过对比svn版本号来计算) 6 | # Create: 2014-12-15 11:21:31 7 | #======================================================================== 8 | 9 | #取最新版本excel 10 | svn up excel/ 2>&1 > /dev/null 11 | 12 | #---------开始计算差异---------------- 13 | all_xls_version_file="all_xls_version.txt" 14 | 15 | #如果没找到版本文件,新建一个 16 | if [ ! -e $all_xls_version_file ]; then 17 | #echo "not found $all_xls_version_file" 18 | touch $all_xls_version_file 19 | fi 20 | 21 | mv $all_xls_version_file ${all_xls_version_file}.old 22 | 23 | #生成新的版本文件; PS:这里的sed的用法你可能会疑惑:) 24 | find excel/ -maxdepth 1 -type f -name "*[.xls|.xlsx]" | xargs svn info | egrep "Name|Rev:" | sed 'N;s/\n/:/' | awk -F ':' '{print $2 $4}' > $all_xls_version_file 25 | 26 | #对比两个版本,取出变化的xls/xlsx 27 | diff ${all_xls_version_file} ${all_xls_version_file}.old | egrep -o "[^ ]*\.xls|[^ ]*\.xlsx" | sort | uniq 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /rapid_do.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #======================================================================== 3 | # FileName: rapid_do.sh 4 | # Author: kevinlin 5 | # Desc: 快速生成最新配置, 依赖于其它脚本和文件协助 6 | # 和svn关联,计算差异。若非此情况不能必执行些脚本 7 | # @get_xls_changelist.sh -->取差异xlsx 8 | # @excel_cmd.txt --> 各个配置生成规则 9 | # Create: 2014-12-15 12:04:37 10 | # History: 11 | # FIXME: 在取svn commit信息时, 有个报错; svn: When specifying working copy paths, only one target may be given 12 | # LastChange: 2014-12-15 12:04:37 13 | #======================================================================== 14 | 15 | #获得有更新的文件列表 16 | change_list=change_list.txt 17 | all_xls_version=all_xls_version.txt 18 | 19 | ./get_xls_changelist.sh > ${change_list} 20 | 21 | while read line 22 | do 23 | file_name=`echo $line | tr -d ' '` 24 | #显示本次更新修改的内容 25 | version=`grep $file_name ${all_xls_version} | awk '{print $2}'` 26 | echo -e -n "\e[1;31m|---- $file_name --->" 27 | svn log -r${version} excel/${file_name} | sed -n '4,4p' 28 | echo -e "\e[0m" 29 | 30 | #查找变化的xls对应的配置并执行成生命令 31 | grep ${file_name} excel_cmd.txt | grep -v "^[ ]*#.*" | while read line; do echo $line; $line; done 32 | echo 33 | 34 | done < ${change_list} 35 | 36 | 37 | 38 | 39 | --------------------------------------------------------------------------------