#! python
|
|
# -*- coding:utf-8 -*-
|
|
|
|
import json
|
|
import openpyxl
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import color_print
|
|
from slpp.slpp import slpp as lua
|
|
|
|
# array数组模式下,各个栏的分布 第一行行是文档注释,不导出
|
|
ACMT_ROW = 2 # comment row 注释
|
|
ATPE_ROW = 3 # type row 类型
|
|
ASRV_ROW = 4 # server row 服务器
|
|
ACLT_ROW = 5 # client row 客户端
|
|
AKEY_COL = 1 # key column key所在列
|
|
|
|
# object(kv)模式下,各个栏的分布 第一行是文档注释,不导出
|
|
OCMT_COL = 1 # comment column 注释
|
|
OTPE_COL = 2 # type column 类型
|
|
OSRV_COL = 3 # server column 服务器
|
|
OCLT_COL = 4 # client column 客户端
|
|
OCTX_COL = 5 # content column 内容所在列
|
|
OFLG_ROW = 2 # flag object模式下 server client所在行
|
|
|
|
SHEET_FLAG_ROW = 2 # 2列1行表示是array 还是object 或者是不用导出的表
|
|
SHEET_FLAG_COL = 1 # 2列1行表示是array 还是object 或者是不用导出的表
|
|
|
|
# 导出服务器/客户端配置标识
|
|
SRV_FLAG = "server"
|
|
CLT_FLAG = "client"
|
|
|
|
# array数组模式下 是否导出 服务器/客户端 get_list()函数
|
|
ALIST_ROW = 3 # 是否导出get_list()函数的设置行
|
|
ALIST_COL = 1
|
|
SRV_LIST = "slist"
|
|
CLT_LIST = "clist"
|
|
|
|
# 用于标识表的类型 不是这两种就不会导出数据
|
|
ARRAY_FLAG = "array" # 标识表的是array类型的表
|
|
OBJECT_FLAG = "object" # 标识表的是object类型的表
|
|
|
|
KEY_FLAG = "$key_" # array表的 作为Key字段的前缀标识
|
|
KEY_FLAG_LEN = 5 # array表的 作为Key字段的前缀标识长度 用于分割字符串
|
|
|
|
# 支持的数据类型
|
|
# "int":1,"number":2,"int64":3,"string":4, "tuple":5, "list":6, "dict":7 为python原生数据格式
|
|
# "json":8,"lua":9 为json和lua的数据格式
|
|
# excel配置的时候 对应类型字段 填入对应数据类型的有效数据格式的数据就OK
|
|
TYPES = {"int": 1, "number": 2, "int64": 3, "string": 4, "tuple": 5, "list": 6, "dict": 7, "json": 8, "lua": 9}
|
|
|
|
try:
|
|
basestring
|
|
except NameError:
|
|
basestring = str
|
|
|
|
# python3中没有uincode了,int能表示int64
|
|
try:
|
|
long
|
|
except NameError:
|
|
long = int
|
|
|
|
# python3中没有unicode了
|
|
try:
|
|
unicode
|
|
except NameError:
|
|
unicode = str
|
|
|
|
|
|
# 类型转换器
|
|
class ValueConverter(object):
|
|
def __init__(self):
|
|
pass
|
|
|
|
# 在python中,字符串和unicode是不一样的。默认从excel读取的数据都是unicode。
|
|
# str可以通过decode转换为unicode
|
|
# ascii' codec can't encode characters
|
|
# 这个函数在python3中没用
|
|
def to_unicode_str(self, val):
|
|
if isinstance(val, str):
|
|
return val
|
|
elif isinstance(val, unicode):
|
|
return val
|
|
else:
|
|
return str(val).decode("utf8")
|
|
|
|
def to_value(self, val_type, val):
|
|
if "int" == val_type:
|
|
return int(val)
|
|
elif "int64" == val_type:
|
|
return long(val)
|
|
elif "number" == val_type:
|
|
# 去除带小数时的小数点,100.0 ==>> 100
|
|
# number就让它带小数点吧,不然强类型的配置无法正确识别出来
|
|
# if long( val ) == float( val ) : return long( val )
|
|
return float(val)
|
|
elif "string" == val_type:
|
|
return self.to_unicode_str(val)
|
|
elif "json" == val_type:
|
|
return json.loads(val)
|
|
elif "lua" == val_type:
|
|
return lua.decode(val)
|
|
elif "tuple" == val_type:
|
|
return tuple(eval(val))
|
|
elif "list" == val_type:
|
|
return list(eval(val))
|
|
elif "dict" == val_type:
|
|
return dict(eval(val))
|
|
else:
|
|
self.raise_error("invalid type", value)
|
|
|
|
|
|
class Sheet(object):
|
|
|
|
def __init__(self, base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list):
|
|
|
|
self.types = [] # 记录各列字段的类型
|
|
|
|
self.srv_writer = srv_writer
|
|
self.clt_writer = clt_writer
|
|
|
|
self.srv_fields = [] # 服务端各列字段名
|
|
self.clt_fields = [] # 客户端各列字段名
|
|
self.srv_comment = {} # 服务器字段注释
|
|
self.clt_comment = {} # 客户端字段注释
|
|
|
|
self.srv_keys = {} # 服务器key
|
|
self.clt_keys = {} # 客户端key
|
|
|
|
self.srv_is_list = srv_is_list # 客户端是否导致get_list() 列表函数
|
|
self.clt_is_list = clt_is_list # 客户端是否导致get_list() 列表函数
|
|
|
|
self.converter = ValueConverter()
|
|
|
|
self.wb_sheet = wb_sheet
|
|
self.base_name = base_name
|
|
|
|
match_list = re.findall("[a-zA-Z0-9_]+$", base_name)
|
|
if None == match_list or 1 != len(match_list):
|
|
Exception(base_name, "not a legal file name")
|
|
|
|
self.base_file_name = match_list[0]
|
|
|
|
# 记录出错时的行列,方便策划定位问题
|
|
self.error_row = 0
|
|
self.error_col = 0
|
|
|
|
# 记录出错位置
|
|
def mark_error_pos(self, row, col):
|
|
if row > 0: self.error_row = row
|
|
if col > 0: self.error_col = col
|
|
|
|
# 发起一个解析错误
|
|
def raise_error(self, what, val):
|
|
excel_info = format("DOC:%s,SHEET:%s,ROW:%d,COLUMN:%d" % \
|
|
(self.base_name, self.wb_sheet.title, self.error_row, self.error_col))
|
|
raise Exception(what, val, excel_info)
|
|
|
|
def to_value(self, val_type, val):
|
|
try:
|
|
return self.converter.to_value(val_type, val)
|
|
except Exception:
|
|
t, e = sys.exc_info()[:2]
|
|
self.raise_error("ConverError", e)
|
|
|
|
# 解析一个表格
|
|
def decode_sheet(self):
|
|
wb_sheet = self.wb_sheet
|
|
|
|
self.decode_type()
|
|
self.decode_field()
|
|
self.decode_ctx()
|
|
|
|
color_print.printGreen(" covert successfully... sheet name -> %s \n" % wb_sheet.title)
|
|
return True
|
|
|
|
# 写入配置到文件
|
|
def write_one_file(self, ctx, base_path, writer, keys_list, comment_text, is_list):
|
|
# 有些配置可能只导出客户端或只导出服务器
|
|
if not any(ctx):
|
|
return
|
|
|
|
write_file_name = self.base_file_name
|
|
if self.wb_sheet.title.find("+") >= 0 or self.wb_sheet.title.find("-") >= 0:
|
|
match_list = re.findall("[a-zA-Z0-9_]+$", self.wb_sheet.title)
|
|
if 1 == len(match_list):
|
|
write_file_name = write_file_name + '_' + match_list[0]
|
|
|
|
wt = writer(self.base_name, self.wb_sheet.title, write_file_name, keys_list, comment_text, is_list)
|
|
ctx = wt.context(ctx)
|
|
suffix = wt.suffix()
|
|
if None != ctx:
|
|
path = base_path + write_file_name + suffix
|
|
|
|
# 必须为wb,不然无法写入utf-8
|
|
file = open(path, 'wb')
|
|
file.write(ctx.encode("utf-8"))
|
|
file.close()
|
|
|
|
# 分别写入到服务端、客户端的配置文件
|
|
def write_files(self, srv_path, clt_path):
|
|
if None != srv_path and None != self.srv_writer:
|
|
self.write_one_file(self.srv_ctx, srv_path, self.srv_writer, self.srv_keys, self.srv_comment,
|
|
self.srv_is_list)
|
|
if None != clt_path and None != self.clt_writer:
|
|
self.write_one_file(self.clt_ctx, clt_path, self.clt_writer, self.clt_keys, self.clt_comment,
|
|
self.clt_is_list)
|
|
|
|
|
|
# 导出数组类型配置,A1格子的内容有array标识
|
|
class ArraySheet(Sheet):
|
|
|
|
def __init__(self, base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list):
|
|
# 记录导出各行的内容
|
|
self.srv_ctx = []
|
|
self.clt_ctx = []
|
|
|
|
super(ArraySheet, self).__init__(
|
|
base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list)
|
|
|
|
# 解析各列的类型(string、number...)
|
|
def decode_type(self):
|
|
# 第一列没数据,类型可以不填,默认为None,但是这里要占个位
|
|
self.types.append(None)
|
|
|
|
for col_idx in range(AKEY_COL + 1, self.wb_sheet.max_column + 1):
|
|
self.mark_error_pos(ATPE_ROW, col_idx)
|
|
value = self.wb_sheet.cell(row=ATPE_ROW, column=col_idx).value
|
|
|
|
# 单元格为空的时候,wb_sheet.cell(row=1, column=2).value == None
|
|
# 类型那一行必须连续,空白表示后面的数据都不导出了
|
|
if value == None:
|
|
break
|
|
if value not in TYPES:
|
|
self.raise_error("invalid type", value)
|
|
|
|
self.types.append(value)
|
|
|
|
# 解析客户端、服务器的字段名(server、client)那两行
|
|
def decode_one_field(self, fields, row_index, keys):
|
|
key_index = 1
|
|
for col_index in range(AKEY_COL, len(self.types) + 1):
|
|
value = self.wb_sheet.cell(
|
|
row=row_index, column=col_index).value
|
|
# 对于array类型的表 一条数据可以有多个值作为KEY 需要处理一下
|
|
if None != value and value.find(KEY_FLAG) >= 0:
|
|
value = value[KEY_FLAG_LEN:]
|
|
keys[value] = key_index
|
|
key_index = key_index + 1
|
|
|
|
# 对于不需要导出的field,可以为空。即value为None
|
|
fields.append(value)
|
|
|
|
# 解析注释那一行
|
|
def decode_one_comment(self, fields, row_index, key_index):
|
|
for col_index in range(AKEY_COL + 1, len(self.types) + 1):
|
|
value = self.wb_sheet.cell(
|
|
row=row_index, column=col_index).value
|
|
|
|
key = self.wb_sheet.cell(
|
|
row=key_index, column=col_index).value
|
|
|
|
if None == key:
|
|
continue
|
|
|
|
# 对于不需要导出的field,可以为空。即value为None
|
|
if key.find(KEY_FLAG) >= 0:
|
|
key = key[KEY_FLAG_LEN:]
|
|
fields[key] = value
|
|
else:
|
|
fields[key] = value
|
|
|
|
# 导出客户端、服务端字段名(server、client)那一列
|
|
def decode_field(self):
|
|
self.decode_one_field(self.srv_fields, ASRV_ROW, self.srv_keys)
|
|
self.decode_one_field(self.clt_fields, ACLT_ROW, self.clt_keys)
|
|
# 导出服务器和客户端对用字段的注释, 导表的时候可能用到
|
|
self.decode_one_comment(self.srv_comment, ACMT_ROW, ASRV_ROW)
|
|
self.decode_one_comment(self.clt_comment, ACMT_ROW, ACLT_ROW)
|
|
|
|
# 解析出一个格子的内容
|
|
def decode_cell(self, row_idx, col_idx):
|
|
value = self.wb_sheet.cell(row=row_idx, column=col_idx).value
|
|
if None == value:
|
|
return None
|
|
|
|
# 类型是从0下标开始,但是excel的第一列从1开始
|
|
self.mark_error_pos(row_idx, col_idx)
|
|
return self.to_value(self.types[col_idx - 1], value)
|
|
|
|
# 解析出一行的内容
|
|
def decode_row(self, row_idx):
|
|
srv_row = {}
|
|
clt_row = {}
|
|
|
|
# 第一列没数据,从第二列开始解析
|
|
for col_idx in range(AKEY_COL + 1, len(self.types) + 1):
|
|
value = self.decode_cell(row_idx, col_idx)
|
|
if None == value: continue
|
|
|
|
srv_key = self.srv_fields[col_idx - 1]
|
|
clt_key = self.clt_fields[col_idx - 1]
|
|
|
|
if srv_key:
|
|
srv_row[srv_key] = value
|
|
if clt_key:
|
|
clt_row[clt_key] = value
|
|
return srv_row, clt_row # 返回一个tuple
|
|
|
|
# 解析导出的内容
|
|
def decode_ctx(self):
|
|
for row_idx in range(ACLT_ROW + 1, self.wb_sheet.max_row + 1):
|
|
srv_row, clt_row = self.decode_row(row_idx)
|
|
|
|
# 不为空才追加
|
|
if any(srv_row):
|
|
self.srv_ctx.append(srv_row)
|
|
if any(clt_row):
|
|
self.clt_ctx.append(clt_row)
|
|
|
|
|
|
# 导出object类型的结构,A1格子有object标识
|
|
class ObjectSheet(Sheet):
|
|
|
|
def __init__(self, base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list):
|
|
# 记录导出各行的内容
|
|
self.srv_ctx = {}
|
|
self.clt_ctx = {}
|
|
|
|
super(ObjectSheet, self).__init__(
|
|
base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list)
|
|
|
|
# 解析各字段的类型
|
|
def decode_type(self):
|
|
for row_idx in range(OFLG_ROW + 1, self.wb_sheet.max_row + 1):
|
|
self.mark_error_pos(row_idx, OTPE_COL)
|
|
value = self.wb_sheet.cell(row=row_idx, column=OTPE_COL).value
|
|
|
|
# 类型必须连续,遇到空则认为后续数据不再导出
|
|
if value == None:
|
|
break
|
|
if value not in TYPES:
|
|
self.raise_error("invalid type", value)
|
|
|
|
self.types.append(value)
|
|
|
|
# 导出客户端、服务端字段名(server、client)那一列
|
|
def decode_one_field(self, fields, col_idx):
|
|
for row_idx in range(OFLG_ROW + 1, len(self.types) + OFLG_ROW + 1):
|
|
value = self.wb_sheet.cell(row=row_idx, column=col_idx).value
|
|
|
|
# 对于不需要导出的field,可以为空。即value为None
|
|
fields.append(value)
|
|
|
|
# 解析注释那一行
|
|
def decode_one_comment(self, fields, col_idx, key_index):
|
|
for row_idx in range(OFLG_ROW + 1, len(self.types) + OFLG_ROW + 1):
|
|
value = self.wb_sheet.cell(row=row_idx, column=col_idx).value
|
|
|
|
key = self.wb_sheet.cell(row=row_idx, column=key_index).value
|
|
|
|
if None == value:
|
|
continue
|
|
# 导出存在值的fields的注释
|
|
fields[key] = value
|
|
|
|
# 导出客户端、服务端字段名(server、client)那一列
|
|
def decode_field(self):
|
|
self.decode_one_field(self.srv_fields, OSRV_COL)
|
|
self.decode_one_field(self.clt_fields, OCLT_COL)
|
|
# 导出服务器和客户端对用字段的注释, 导表的时候可能用到
|
|
self.decode_one_comment(self.srv_comment, OCMT_COL, OSRV_COL)
|
|
self.decode_one_comment(self.clt_comment, OCMT_COL, OCLT_COL)
|
|
|
|
# 解析一个单元格内容
|
|
def decode_cell(self, row_idx):
|
|
value = self.wb_sheet.cell(row=row_idx, column=OCTX_COL).value
|
|
if None == value: return None
|
|
|
|
# 在object的结构中,数据是从第二行开始的,所以types的下标偏移2
|
|
self.mark_error_pos(row_idx, OCTX_COL)
|
|
return self.to_value(self.types[row_idx - OFLG_ROW - 1], value)
|
|
|
|
# 解析表格的所有内容
|
|
def decode_ctx(self):
|
|
for row_idx in range(OFLG_ROW + 1, len(self.types) + OFLG_ROW + 1):
|
|
value = self.decode_cell(row_idx)
|
|
|
|
if None == value:
|
|
continue
|
|
|
|
srv_key = self.srv_fields[row_idx - OFLG_ROW - 1]
|
|
clt_key = self.clt_fields[row_idx - OFLG_ROW - 1]
|
|
|
|
if srv_key: self.srv_ctx[srv_key] = value
|
|
if clt_key: self.clt_ctx[clt_key] = value
|
|
|
|
|
|
class ExcelDoc:
|
|
|
|
def __init__(self, file, abspath):
|
|
self.file = file
|
|
self.abspath = abspath
|
|
|
|
# 是否需要解析
|
|
# 返回解析的对象类型
|
|
def need_decode(self, wb_sheet):
|
|
sheet_val = wb_sheet.cell(
|
|
row=SHEET_FLAG_ROW, column=SHEET_FLAG_COL).value
|
|
|
|
sheeter = None
|
|
srv_value = None
|
|
clt_value = None
|
|
srv_is_list = False
|
|
clt_is_list = False
|
|
if ARRAY_FLAG == sheet_val:
|
|
if wb_sheet.max_row <= ACLT_ROW or wb_sheet.max_column <= AKEY_COL:
|
|
return None
|
|
|
|
sheeter = ArraySheet
|
|
srv_value = wb_sheet.cell(row=ASRV_ROW, column=AKEY_COL).value
|
|
clt_value = wb_sheet.cell(row=ACLT_ROW, column=AKEY_COL).value
|
|
|
|
is_list_val = wb_sheet.cell(
|
|
row=ALIST_ROW, column=ALIST_COL).value
|
|
if None != is_list_val and is_list_val.find(SRV_LIST) >= 0:
|
|
srv_is_list = True
|
|
if None != is_list_val and is_list_val.find(CLT_LIST) >= 0:
|
|
clt_is_list = True
|
|
|
|
elif OBJECT_FLAG == sheet_val:
|
|
sheeter = ObjectSheet
|
|
srv_value = wb_sheet.cell(row=OFLG_ROW, column=OSRV_COL).value
|
|
clt_value = wb_sheet.cell(row=OFLG_ROW, column=OCLT_COL).value
|
|
else:
|
|
return None, srv_is_list, clt_is_list
|
|
|
|
# 没有这两个标识就不是配置表。可能是策划的一些备注说明
|
|
if SRV_FLAG != srv_value or CLT_FLAG != clt_value:
|
|
return None, srv_is_list, clt_is_list
|
|
return sheeter, srv_is_list, clt_is_list
|
|
|
|
def decode(self, srv_path, clt_path, srv_writer, clt_writer):
|
|
color_print.printYellow(" start covert: %s \n" % self.file.ljust(44, "*"))
|
|
base_name = os.path.splitext(self.file)[0]
|
|
wb = openpyxl.load_workbook(self.abspath)
|
|
|
|
for wb_sheet in wb.worksheets:
|
|
Sheeter, srv_is_list, clt_is_list = self.need_decode(wb_sheet)
|
|
if None == Sheeter:
|
|
color_print.printPink(" covert skip........... sheet name -> %s\n" % wb_sheet.title)
|
|
continue
|
|
|
|
sheet = Sheeter(base_name, wb_sheet, srv_writer, clt_writer, srv_is_list, clt_is_list)
|
|
if sheet.decode_sheet():
|
|
sheet.write_files(srv_path, clt_path)
|