基于openpyxl的excel转换工具。支持xlsx文件转换为erlang,elixir,lua,json,xml,python等配置文件
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

458 lines
17 KiB

6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
6 years ago
6 years ago
6 years ago
4 years ago
4 years ago
6 years ago
4 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
4 years ago
6 years ago
6 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
6 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
4 years ago
4 years ago
6 years ago
4 years ago
6 years ago
4 years ago
  1. #! python
  2. # -*- coding:utf-8 -*-
  3. import json
  4. import openpyxl
  5. import os
  6. import re
  7. import sys
  8. import color_print
  9. from slpp.slpp import slpp as lua
  10. # array数组模式下,各个栏的分布 第一行行是文档注释,不导出
  11. ACMT_ROW = 2 # comment row 注释
  12. ATPE_ROW = 3 # type row 类型
  13. ASRV_ROW = 4 # server row 服务器
  14. ACLT_ROW = 5 # client row 客户端
  15. AKEY_COL = 1 # key column key所在列
  16. # object(kv)模式下,各个栏的分布 第一行是文档注释,不导出
  17. OCMT_COL = 1 # comment column 注释
  18. OTPE_COL = 2 # type column 类型
  19. OSRV_COL = 3 # server column 服务器
  20. OCLT_COL = 4 # client column 客户端
  21. OCTX_COL = 5 # content column 内容所在列
  22. OFLG_ROW = 2 # flag object模式下 server client所在行
  23. SHEET_FLAG_ROW = 2 # 2列1行表示是array 还是object 或者是不用导出的表
  24. SHEET_FLAG_COL = 1 # 2列1行表示是array 还是object 或者是不用导出的表
  25. # 导出服务器/客户端配置标识
  26. SRV_FLAG = "server"
  27. CLT_FLAG = "client"
  28. # array数组模式下 是否导出 服务器/客户端 get_list()函数
  29. ALIST_ROW = 3 # 是否导出get_list()函数的设置行
  30. ALIST_COL = 1
  31. SRV_LIST = "slist"
  32. CLT_LIST = "clist"
  33. # 用于标识表的类型 不是这两种就不会导出数据
  34. ARRAY_FLAG = "array" # 标识表的是array类型的表
  35. OBJECT_FLAG = "object" # 标识表的是object类型的表
  36. KEY_FLAG = "$key_" # array表的 作为Key字段的前缀标识
  37. KEY_FLAG_LEN = 5 # array表的 作为Key字段的前缀标识长度 用于分割字符串
  38. # 支持的数据类型
  39. # "int":1,"number":2,"int64":3,"string":4, "tuple":5, "list":6, "dict":7 为python原生数据格式
  40. # "json":8,"lua":9 为json和lua的数据格式
  41. # excel配置的时候 对应类型字段 填入对应数据类型的有效数据格式的数据就OK
  42. TYPES = {"int": 1, "number": 2, "int64": 3, "string": 4, "tuple": 5, "list": 6, "dict": 7, "json": 8, "lua": 9}
  43. try:
  44. basestring
  45. except NameError:
  46. basestring = str
  47. # python3中没有uincode了,int能表示int64
  48. try:
  49. long
  50. except NameError:
  51. long = int
  52. # python3中没有unicode了
  53. try:
  54. unicode
  55. except NameError:
  56. unicode = str
  57. # 类型转换器
  58. class ValueConverter(object):
  59. def __init__(self):
  60. pass
  61. # 在python中,字符串和unicode是不一样的。默认从excel读取的数据都是unicode。
  62. # str可以通过decode转换为unicode
  63. # ascii' codec can't encode characters
  64. # 这个函数在python3中没用
  65. def to_unicode_str(self, val):
  66. if isinstance(val, str):
  67. return val
  68. elif isinstance(val, unicode):
  69. return val
  70. else:
  71. return str(val).decode("utf8")
  72. def to_value(self, val_type, val):
  73. if "int" == val_type:
  74. return int(val)
  75. elif "int64" == val_type:
  76. return long(val)
  77. elif "number" == val_type:
  78. # 去除带小数时的小数点,100.0 ==>> 100
  79. # number就让它带小数点吧,不然强类型的配置无法正确识别出来
  80. # if long( val ) == float( val ) : return long( val )
  81. return float(val)
  82. elif "string" == val_type:
  83. return self.to_unicode_str(val)
  84. elif "json" == val_type:
  85. return json.loads(val)
  86. elif "lua" == val_type:
  87. return lua.decode(val)
  88. elif "tuple" == val_type:
  89. return tuple(eval(val))
  90. elif "list" == val_type:
  91. return list(eval(val))
  92. elif "dict" == val_type:
  93. return dict(eval(val))
  94. else:
  95. self.raise_error("invalid type", value)
  96. class Sheet(object):
  97. def __init__(self, base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list):
  98. self.types = [] # 记录各列字段的类型
  99. self.srv_writer = srv_writer
  100. self.clt_writer = clt_writer
  101. self.srv_fields = [] # 服务端各列字段名
  102. self.clt_fields = [] # 客户端各列字段名
  103. self.srv_comment = {} # 服务器字段注释
  104. self.clt_comment = {} # 客户端字段注释
  105. self.srv_keys = {} # 服务器key
  106. self.clt_keys = {} # 客户端key
  107. self.srv_is_list = srv_is_list # 客户端是否导致get_list() 列表函数
  108. self.clt_is_list = clt_is_list # 客户端是否导致get_list() 列表函数
  109. self.converter = ValueConverter()
  110. self.wb_sheet = wb_sheet
  111. self.base_name = base_name
  112. match_list = re.findall("[a-zA-Z0-9_]+$", base_name)
  113. if None == match_list or 1 != len(match_list):
  114. Exception(base_name, "not a legal file name")
  115. self.base_file_name = match_list[0]
  116. # 记录出错时的行列,方便策划定位问题
  117. self.error_row = 0
  118. self.error_col = 0
  119. # 记录出错位置
  120. def mark_error_pos(self, row, col):
  121. if row > 0: self.error_row = row
  122. if col > 0: self.error_col = col
  123. # 发起一个解析错误
  124. def raise_error(self, what, val):
  125. excel_info = format("DOC:%s,SHEET:%s,ROW:%d,COLUMN:%d" % \
  126. (self.base_name, self.wb_sheet.title, self.error_row, self.error_col))
  127. raise Exception(what, val, excel_info)
  128. def to_value(self, val_type, val):
  129. try:
  130. return self.converter.to_value(val_type, val)
  131. except Exception:
  132. t, e = sys.exc_info()[:2]
  133. self.raise_error("ConverError", e)
  134. # 解析一个表格
  135. def decode_sheet(self):
  136. wb_sheet = self.wb_sheet
  137. self.decode_type()
  138. self.decode_field()
  139. self.decode_ctx()
  140. color_print.printGreen(" covert successfully... sheet name -> %s \n" % wb_sheet.title)
  141. return True
  142. # 写入配置到文件
  143. def write_one_file(self, ctx, base_path, writer, keys_list, comment_text, is_list):
  144. # 有些配置可能只导出客户端或只导出服务器
  145. if not any(ctx):
  146. return
  147. write_file_name = self.base_file_name
  148. if self.wb_sheet.title.find("+") >= 0 or self.wb_sheet.title.find("-") >= 0:
  149. match_list = re.findall("[a-zA-Z0-9_]+$", self.wb_sheet.title)
  150. if 1 == len(match_list):
  151. write_file_name = write_file_name + '_' + match_list[0]
  152. wt = writer(self.base_name, self.wb_sheet.title, write_file_name, keys_list, comment_text, is_list)
  153. ctx = wt.context(ctx)
  154. suffix = wt.suffix()
  155. if None != ctx:
  156. path = base_path + write_file_name + suffix
  157. # 必须为wb,不然无法写入utf-8
  158. file = open(path, 'wb')
  159. file.write(ctx.encode("utf-8"))
  160. file.close()
  161. # 分别写入到服务端、客户端的配置文件
  162. def write_files(self, srv_path, clt_path):
  163. if None != srv_path and None != self.srv_writer:
  164. self.write_one_file(self.srv_ctx, srv_path, self.srv_writer, self.srv_keys, self.srv_comment,
  165. self.srv_is_list)
  166. if None != clt_path and None != self.clt_writer:
  167. self.write_one_file(self.clt_ctx, clt_path, self.clt_writer, self.clt_keys, self.clt_comment,
  168. self.clt_is_list)
  169. # 导出数组类型配置,A1格子的内容有array标识
  170. class ArraySheet(Sheet):
  171. def __init__(self, base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list):
  172. # 记录导出各行的内容
  173. self.srv_ctx = []
  174. self.clt_ctx = []
  175. super(ArraySheet, self).__init__(
  176. base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list)
  177. # 解析各列的类型(string、number...)
  178. def decode_type(self):
  179. # 第一列没数据,类型可以不填,默认为None,但是这里要占个位
  180. self.types.append(None)
  181. for col_idx in range(AKEY_COL + 1, self.wb_sheet.max_column + 1):
  182. self.mark_error_pos(ATPE_ROW, col_idx)
  183. value = self.wb_sheet.cell(row=ATPE_ROW, column=col_idx).value
  184. # 单元格为空的时候,wb_sheet.cell(row=1, column=2).value == None
  185. # 类型那一行必须连续,空白表示后面的数据都不导出了
  186. if value == None:
  187. break
  188. if value not in TYPES:
  189. self.raise_error("invalid type", value)
  190. self.types.append(value)
  191. # 解析客户端、服务器的字段名(server、client)那两行
  192. def decode_one_field(self, fields, row_index, keys):
  193. key_index = 1
  194. for col_index in range(AKEY_COL, len(self.types) + 1):
  195. value = self.wb_sheet.cell(
  196. row=row_index, column=col_index).value
  197. # 对于array类型的表 一条数据可以有多个值作为KEY 需要处理一下
  198. if None != value and value.find(KEY_FLAG) >= 0:
  199. value = value[KEY_FLAG_LEN:]
  200. keys[value] = key_index
  201. key_index = key_index + 1
  202. # 对于不需要导出的field,可以为空。即value为None
  203. fields.append(value)
  204. # 解析注释那一行
  205. def decode_one_comment(self, fields, row_index, key_index):
  206. for col_index in range(AKEY_COL + 1, len(self.types) + 1):
  207. value = self.wb_sheet.cell(
  208. row=row_index, column=col_index).value
  209. key = self.wb_sheet.cell(
  210. row=key_index, column=col_index).value
  211. if None == key:
  212. continue
  213. # 对于不需要导出的field,可以为空。即value为None
  214. if key.find(KEY_FLAG) >= 0:
  215. key = key[KEY_FLAG_LEN:]
  216. fields[key] = value
  217. else:
  218. fields[key] = value
  219. # 导出客户端、服务端字段名(server、client)那一列
  220. def decode_field(self):
  221. self.decode_one_field(self.srv_fields, ASRV_ROW, self.srv_keys)
  222. self.decode_one_field(self.clt_fields, ACLT_ROW, self.clt_keys)
  223. # 导出服务器和客户端对用字段的注释, 导表的时候可能用到
  224. self.decode_one_comment(self.srv_comment, ACMT_ROW, ASRV_ROW)
  225. self.decode_one_comment(self.clt_comment, ACMT_ROW, ACLT_ROW)
  226. # 解析出一个格子的内容
  227. def decode_cell(self, row_idx, col_idx):
  228. value = self.wb_sheet.cell(row=row_idx, column=col_idx).value
  229. if None == value:
  230. return None
  231. # 类型是从0下标开始,但是excel的第一列从1开始
  232. self.mark_error_pos(row_idx, col_idx)
  233. return self.to_value(self.types[col_idx - 1], value)
  234. # 解析出一行的内容
  235. def decode_row(self, row_idx):
  236. srv_row = {}
  237. clt_row = {}
  238. # 第一列没数据,从第二列开始解析
  239. for col_idx in range(AKEY_COL + 1, len(self.types) + 1):
  240. value = self.decode_cell(row_idx, col_idx)
  241. if None == value: continue
  242. srv_key = self.srv_fields[col_idx - 1]
  243. clt_key = self.clt_fields[col_idx - 1]
  244. if srv_key:
  245. srv_row[srv_key] = value
  246. if clt_key:
  247. clt_row[clt_key] = value
  248. return srv_row, clt_row # 返回一个tuple
  249. # 解析导出的内容
  250. def decode_ctx(self):
  251. for row_idx in range(ACLT_ROW + 1, self.wb_sheet.max_row + 1):
  252. srv_row, clt_row = self.decode_row(row_idx)
  253. # 不为空才追加
  254. if any(srv_row):
  255. self.srv_ctx.append(srv_row)
  256. if any(clt_row):
  257. self.clt_ctx.append(clt_row)
  258. # 导出object类型的结构,A1格子有object标识
  259. class ObjectSheet(Sheet):
  260. def __init__(self, base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list):
  261. # 记录导出各行的内容
  262. self.srv_ctx = {}
  263. self.clt_ctx = {}
  264. super(ObjectSheet, self).__init__(
  265. base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list)
  266. # 解析各字段的类型
  267. def decode_type(self):
  268. for row_idx in range(OFLG_ROW + 1, self.wb_sheet.max_row + 1):
  269. self.mark_error_pos(row_idx, OTPE_COL)
  270. value = self.wb_sheet.cell(row=row_idx, column=OTPE_COL).value
  271. # 类型必须连续,遇到空则认为后续数据不再导出
  272. if value == None:
  273. break
  274. if value not in TYPES:
  275. self.raise_error("invalid type", value)
  276. self.types.append(value)
  277. # 导出客户端、服务端字段名(server、client)那一列
  278. def decode_one_field(self, fields, col_idx):
  279. for row_idx in range(OFLG_ROW + 1, len(self.types) + OFLG_ROW + 1):
  280. value = self.wb_sheet.cell(row=row_idx, column=col_idx).value
  281. # 对于不需要导出的field,可以为空。即value为None
  282. fields.append(value)
  283. # 解析注释那一行
  284. def decode_one_comment(self, fields, col_idx, key_index):
  285. for row_idx in range(OFLG_ROW + 1, len(self.types) + OFLG_ROW + 1):
  286. value = self.wb_sheet.cell(row=row_idx, column=col_idx).value
  287. key = self.wb_sheet.cell(row=row_idx, column=key_index).value
  288. if None == value:
  289. continue
  290. # 导出存在值的fields的注释
  291. fields[key] = value
  292. # 导出客户端、服务端字段名(server、client)那一列
  293. def decode_field(self):
  294. self.decode_one_field(self.srv_fields, OSRV_COL)
  295. self.decode_one_field(self.clt_fields, OCLT_COL)
  296. # 导出服务器和客户端对用字段的注释, 导表的时候可能用到
  297. self.decode_one_comment(self.srv_comment, OCMT_COL, OSRV_COL)
  298. self.decode_one_comment(self.clt_comment, OCMT_COL, OCLT_COL)
  299. # 解析一个单元格内容
  300. def decode_cell(self, row_idx):
  301. value = self.wb_sheet.cell(row=row_idx, column=OCTX_COL).value
  302. if None == value: return None
  303. # 在object的结构中,数据是从第二行开始的,所以types的下标偏移2
  304. self.mark_error_pos(row_idx, OCTX_COL)
  305. return self.to_value(self.types[row_idx - OFLG_ROW - 1], value)
  306. # 解析表格的所有内容
  307. def decode_ctx(self):
  308. for row_idx in range(OFLG_ROW + 1, len(self.types) + OFLG_ROW + 1):
  309. value = self.decode_cell(row_idx)
  310. if None == value:
  311. continue
  312. srv_key = self.srv_fields[row_idx - OFLG_ROW - 1]
  313. clt_key = self.clt_fields[row_idx - OFLG_ROW - 1]
  314. if srv_key: self.srv_ctx[srv_key] = value
  315. if clt_key: self.clt_ctx[clt_key] = value
  316. class ExcelDoc:
  317. def __init__(self, file, abspath):
  318. self.file = file
  319. self.abspath = abspath
  320. # 是否需要解析
  321. # 返回解析的对象类型
  322. def need_decode(self, wb_sheet):
  323. sheet_val = wb_sheet.cell(
  324. row=SHEET_FLAG_ROW, column=SHEET_FLAG_COL).value
  325. sheeter = None
  326. srv_value = None
  327. clt_value = None
  328. srv_is_list = False
  329. clt_is_list = False
  330. if ARRAY_FLAG == sheet_val:
  331. if wb_sheet.max_row <= ACLT_ROW or wb_sheet.max_column <= AKEY_COL:
  332. return None
  333. sheeter = ArraySheet
  334. srv_value = wb_sheet.cell(row=ASRV_ROW, column=AKEY_COL).value
  335. clt_value = wb_sheet.cell(row=ACLT_ROW, column=AKEY_COL).value
  336. is_list_val = wb_sheet.cell(
  337. row=ALIST_ROW, column=ALIST_COL).value
  338. if None != is_list_val and is_list_val.find(SRV_LIST) >= 0:
  339. srv_is_list = True
  340. if None != is_list_val and is_list_val.find(CLT_LIST) >= 0:
  341. clt_is_list = True
  342. elif OBJECT_FLAG == sheet_val:
  343. sheeter = ObjectSheet
  344. srv_value = wb_sheet.cell(row=OFLG_ROW, column=OSRV_COL).value
  345. clt_value = wb_sheet.cell(row=OFLG_ROW, column=OCLT_COL).value
  346. else:
  347. return None, srv_is_list, clt_is_list
  348. # 没有这两个标识就不是配置表。可能是策划的一些备注说明
  349. if SRV_FLAG != srv_value or CLT_FLAG != clt_value:
  350. return None, srv_is_list, clt_is_list
  351. return sheeter, srv_is_list, clt_is_list
  352. def decode(self, srv_path, clt_path, srv_writer, clt_writer):
  353. color_print.printYellow(" start covert: %s \n" % self.file.ljust(44, "*"))
  354. base_name = os.path.splitext(self.file)[0]
  355. wb = openpyxl.load_workbook(self.abspath)
  356. for wb_sheet in wb.worksheets:
  357. Sheeter, srv_is_list, clt_is_list = self.need_decode(wb_sheet)
  358. if None == Sheeter:
  359. color_print.printPink(" covert skip........... sheet name -> %s\n" % wb_sheet.title)
  360. continue
  361. sheet = Sheeter(base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list)
  362. if sheet.decode_sheet():
  363. sheet.write_files(srv_path, clt_path)