# -*- coding: UTF-8 -*- import multiprocessing import time import xlrd import os import shutil import re import sys import json from functools import cmp_to_key import hashlib import threading import concurrent.futures INT_KEY_TYPE = "int_key" UINT_KEY_TYPE = "uint_key" STRING_KEY_TYPE = "string_key" INT_TYPE = "int" FLOAT_TYPE = "float" UINI_TYPE = "uint" STRING_TYPE = "string" STRING_LAN_TYPE = "string_lan" STRING_LUA_TYPE = "string_lua" STRING_LIST_TYPE = "string[]" INT_LSIT_TYPE = "int[]" UINT_LSIT_TYPE = "uint[]" LONG_LSIT_TYPE = "long[]" FLOAT_LIST_TYPE = "float[]" LONG_TYPE = "long" LONG_LIST_TYPE = "long[]" NONE_TYPE = "none" LUA_WORD = "(LUA)" EXCEL_DIR = "./Excel" GENERATE_LUA_DIR = "../../pandora_cli_proj/pandora/Assets/Lua/Desc" GENERATE_LUA_LANG_DIR = "../../pandora_cli_proj/pandora/Assets/Lua/Desc/LanguageDesc" EXCEL_SUFFIX = ".xlsx" TEMP_EXCEL_PREFIX = "~$" #md5检查 MD5_FILE = "./md5.txt" md5_file_dict = {} #是否快速导表 is_fast_export = False #是否使用多进程 is_use_multi_thread = True #总共导出的表数量 export_excel_count = 0 export_excel_index = 0 #创建一个线程锁对象 lock = threading.Lock() pool = concurrent.futures.ThreadPoolExecutor(max_workers=8) #缓存表格数据 excel_data_dict = {} alone_save_excel = [ ] remove_empty_table_excel = [ "ABTestConfigRule.xlsx", "MainlandGuideMovie.xlsx" ] excel_config_file = "ClientExcelConfig.json" excel_self_config_file = "./selfExcelConfig/ClientExcelConfig.json" class FirstColumn: def __init__(self, key): self._rawKey = key self._key = key self._list_count = 1 self._list_index = 0 self._inew_table = 0 self.parse() def parse(self): match = re.match(r"(\w+)_(\d+)_(\d+)", self._key) if match: self._key = match.group(1) self._list_count = int(match.group(2)) self._list_index = int(match.group(3)) match2 = re.match(r"(\w+)_(\d+)_(\w+)",self._key) if match2: self._key = match2.group(1) self._list_index = int(match2.group(2)) self._list_key = match2.group(3) self._inew_table = 1 def get_key(self): return self._key def get_list_key(self): return self._list_key def is_list(self): return self._list_count > 1 def get_list_index(self): return self._list_index def is_newtable(self): return self._inew_table > 0 def get_cell_value(value_type, value,key=None): if value is None or value == "": return "nil" elif value_type == INT_KEY_TYPE or value_type == INT_TYPE or value_type == UINT_KEY_TYPE or value_type == UINI_TYPE or value_type == LONG_TYPE: return int(round(float(value))) elif value_type.startswith(INT_LSIT_TYPE): # if type(value) is str: # value = value.encode("utf8") value = str(value) value = value.replace(',',';') value = value.split(';') value = [int(round(float(x))) for x in value] return value elif value_type.startswith(UINT_LSIT_TYPE): # if type(value) is str: # value = value.encode("utf8") value = str(value) value = value.replace(',',';') value = value.split(';') value = [int(round(float(x))) for x in value] return value elif value_type.startswith(LONG_LIST_TYPE): # if type(value) is str: # value = value.encode("utf8") value = str(value) value = value.replace(',',';') value = value.split(';') value = [int(round(float(x))) for x in value] return value elif value_type.startswith(FLOAT_LIST_TYPE): # if type(value) is str: # value = value.encode("utf8") value = str(value) value = value.replace(',',';') value = value.split(';') value = [float(x) for x in value] return value elif value_type == FLOAT_TYPE: return float(value) elif value_type.startswith(STRING_LIST_TYPE): # if type(value) is str: # value = value.encode("utf8") value = str(value) value = value.replace(',',';') value = value.split(';') value = [x.strip() for x in value] return value elif value_type == STRING_KEY_TYPE or value_type == STRING_TYPE or value_type == STRING_LAN_TYPE: # if type(value) is str or type(value) is str: # value = value.encode("utf8") value = str(value) #语言表 value = value.replace("\n", "\\n") value = value.replace("\"", "\\\"") value = value.replace("", "%s") value = value.replace("", "%s") value = value.replace("", "%s") value = value.replace("", "%s") return value elif value_type == STRING_LUA_TYPE: return LUA_WORD+str(value) # elif value_type == STRING_LAN_TYPE: # value = value.encode("utf8") # lang_key = str(value).replace("#", "") # return LUA_WORD+"GM and GM.Lang(\"%s\") or \"%s\"" % (lang_key, lang_key) return None def sort_spell_skin_show(table): items = [] for key in table: items.append(table[key]) def sort_method(item1,item2): if item1["spellId"] < item2["spellId"]: return -1 elif item1["spellId"] > item2["spellId"]: return 1 else: if item1["skinIndex"] < item2["skinIndex"]: return -1 elif item1["skinIndex"] > item2["skinIndex"]: return 1 else: if item1.get("layer") != "nil": if item1["layer"] < item2["layer"]: return -1 else: return 1 else: return 0 sorted_items = sorted(items,key = cmp_to_key(sort_method)) return sorted_items def excel_to_lua_data(path, isLanguage): global total_read_time excel_data = {} excel_name = os.path.basename(path) xml_data = xlrd.open_workbook(path) # 打开xls文件 sheet = xml_data.sheets()[0] # 打开第一张表 # 先判断key的数量 row_num = sheet.nrows # 获取表的行数 col_num = sheet.ncols # 获取表的列数 row_key_values = sheet.row_values(0) # 获取表的第一行的所有数据 row_type_values = sheet.row_values(1) # 获取表的第二行的所有数据 row_cs_values = sheet.row_values(2) # 获取表的第三行的所有数据 row_desc_values = sheet.row_values(3) # 获取表的第四行的所有数据 index_list = [] # 需要导出的列索引 key_list_map = {} # 所有key_value的列表 key_list = [] # key的列表 for i in range(0, col_num): key_value = row_key_values[i] type_value = row_type_values[i] cs_value = row_cs_values[i] if type_value == INT_KEY_TYPE or type_value == UINT_KEY_TYPE or type_value == STRING_KEY_TYPE: key_list.append(key_value) if key_value not in key_list_map: key_list_map[key_value] = [] for row in range(4, row_num): try: key_list_map[key_value].append(get_cell_value(type_value, sheet.row_values(row)[i])) except: print("error file:" + path + ",key:" + key_value + ",row:" + str(row)) sys.exit() #忽略的部分列 if key_value != NONE_TYPE and cs_value != "skip" and cs_value.find("c") != -1: if isLanguage: if type_value != STRING_KEY_TYPE: index_list.append(i) else: index_list.append(i) def set_row_value(row, obj): row_values = sheet.row_values(row) struct_key_list = [] for i in index_list: first_column = FirstColumn(row_key_values[i]) column_key = first_column.get_key() try: #数组单独处理 if first_column.is_list(): if column_key not in obj: obj[column_key] = {} obj[column_key][first_column.get_list_index()+1] = get_cell_value(row_type_values[i], row_values[i]) elif first_column.is_newtable(): if column_key not in obj: obj[column_key] = {} if struct_key_list.count(column_key) == 0: struct_key_list.append(column_key) if (first_column.get_list_index()+1) not in obj[column_key]: obj[column_key][first_column.get_list_index()+1] = {} obj[column_key][first_column.get_list_index()+1][first_column.get_list_key()] = get_cell_value(row_type_values[i], row_values[i]) else: obj[column_key] = get_cell_value(row_type_values[i], row_values[i]) except: print("error file:" + str(path) + ",key:" + str(column_key), ",row:" + str(row)) sys.exit() if excel_name in remove_empty_table_excel: for key in struct_key_list: struct_list_indexes = obj[key].keys() for index in struct_list_indexes: struct_keys = list(obj[key][index].values()) if len(struct_keys) == struct_keys.count("nil"): obj[key][index] = None values = list(obj[key].values()) if len(values) == values.count(None): obj.pop(key) table = {} if len(key_list) == 2: #暂时只支持2个key row_index = 0 for row in range(4, row_num): first_key = key_list_map[key_list[0]][row_index] second_key = key_list_map[key_list[1]][row_index] row_index = row_index + 1 if first_key == "" or first_key == "nil" or second_key == "" or second_key == "nil": print("%s key is nil,row:%d" % (path, row)) continue if first_key not in table: table[first_key] = {} if second_key not in table[first_key]: table[first_key][second_key] = {} obj = table[first_key][second_key] set_row_value(row, obj) elif len(key_list) <= 1: #没有key或者单个key row_index = 0 row_key_list = None if len(key_list) > 0: row_key_list = key_list_map[key_list[0]] for row in range(4, row_num): row_index = row_index + 1 row_key = row_index if not (row_key_list is None): row_key = row_key_list[row_index-1] if row_key == "nil" or row_key == "": print("%s key is nil,row:%d" % (path, row)) continue if row_key not in table: table[row_key] = {} obj = table[row_key] set_row_value(row, obj) file_name = os.path.basename(path) if file_name == "SpellActionSkinShowDesc.xlsx": try: table = sort_spell_skin_show(table) except Exception as e: print(e) return obj_to_lua(table) ####################################################################### def value_tostring(value): value_type = type(value) if value_type is str: value = str(value) value_type = type(value) if value is None: return "nil" if value_type == str: if value == "" or value == "nil": return "nil" if value.find(LUA_WORD, 0, len(LUA_WORD)) == -1: return "\"%s\"" % value else: return value.replace(LUA_WORD, "") elif value_type == bool: if value: return "true" else: return "false" else: return value def key_tostring(key): key_type = type(key) if key_type == int: return "[%d]" % key elif key_type == str: return key def obj_to_lua(obj): result = [] def _encode_to_lua(value, key, indent): if value is None: return key_type = type(key) if key_type != str and key_type != int: return value_type = type(value) if value_type == list or value_type == dict: result.append("%s%s = {" % (indent, key_tostring(key))) indent2 = indent + "\t" if value_type == list: for i in range(len(value)): _encode_to_lua(value[i], i + 1, indent2) elif value_type == dict: for k in list(value.keys()): _encode_to_lua(value[k], k, indent2) if indent == "": result.append("%s}" % (indent)) else: result.append("%s}," % (indent)) else: valueStr = value_tostring(value) if valueStr != "nil": result.append("%s%s = %s," % (indent, key_tostring(key), valueStr)) _encode_to_lua(obj, "local M", "") result.append("return M") return "\n".join(result) def excel_to_lua_file(excel_path, lua_path, isLanguage): #打印时间 start_time = time.time() if not os.path.exists(excel_path): print("excel not exists.", excel_path) return try: lua_data = excel_to_lua_data(excel_path, isLanguage) end_time1 = time.time() f = open(lua_path, "w", encoding="UTF-8",newline="\n") f.write(lua_data) f.close() except Exception as e: print(e) print(excel_path+" 配置错误,检查该表") exit(-1) end_time2 = time.time() cost1 = round(end_time1 - start_time, 2) cost2 = round(end_time2 - start_time, 2) #print(f"export excel to lua success. {excel_path} cost1 {cost1}s cost2 {cost2}") def thread_func(name, excel_file_path, lua_file_path, is_language): excel_to_lua_file(excel_file_path, lua_file_path, is_language) # #写入md5文件 # excel_md5 = get_md5(excel_file_path) # lua_md5 = get_md5(lua_file_path) # lock.acquire() # export_excel_index += 1 # #print("thread_func finish ", name, "export_excel_index:", export_excel_index) # md5_file_dict[excel_file_path] = excel_md5 # md5_file_dict[lua_file_path] = lua_md5 # lock.release() print("thread_func finish ", name) #是否跳过导表 def is_skip_export(excel_path, lua_path): if not os.path.exists(excel_path): #print("excel not exists.", excel_path) return True if not os.path.exists(lua_path): return False #如果是快速导表,检查md5,excel_path跟lua_path都没变化,不导表 if is_fast_export: if not is_md5_change(excel_path) and not is_md5_change(lua_path): return True return False #读取md5文件 def read_md5_file(): if not os.path.exists(MD5_FILE): return{} f = open(MD5_FILE, "r", encoding="UTF-8") lines = f.readlines() f.close() if len(lines) == 0: return {} md5_dict = {} for i in range(0, len(lines)): line = lines[i].strip() if line == "": continue line_list = line.split(" ") if len(line_list) != 2: continue md5_dict[line_list[0]] = line_list[1] return md5_dict #判断md5是否改变 def is_md5_change(file_name): #字典中没有该文件 if file_name not in md5_file_dict: #print("md5_file_dict not have file:",file_name) return True new_md5 = get_md5(file_name) old_md5 = md5_file_dict[file_name] if new_md5 != old_md5: #print("md5 change:",file_name, "old_md5:",old_md5,"new_md5:",new_md5) return True return False #获取指定文件的md5 def get_md5(file_name): if not os.path.exists(file_name): return "" f = open(file_name, "rb") md5 = hashlib.md5(f.read()).hexdigest() f.close() #小写 return md5.lower() #写入md5文件 def write_md5_file(md5_dict): #覆盖写入 f = open(MD5_FILE, "w", encoding="UTF-8") for key in md5_dict: f.write(key + " " + md5_dict[key] + "\n") f.close() #导出所有表 def excel_list_to_lua_file(file_path, configs, export_all_file, export_paths, is_use_multi_thread, pool, change_file_list): if export_all_file: excel_names = os.listdir(file_path) for name in excel_names: if name in configs['included'] and name.endswith(EXCEL_SUFFIX) and not name.startswith(TEMP_EXCEL_PREFIX): excel_file_path = file_path + os.sep + name lua_file_name = name[:name.find(EXCEL_SUFFIX)] lua_dir = GENERATE_LUA_DIR if name == "LanguageDesc.xlsx": lua_dir = GENERATE_LUA_LANG_DIR if lua_file_name in alone_save_excel: print("need alone save",name) lua_file_dir = lua_dir + os.sep + lua_file_name if not os.path.exists(lua_file_dir): os.mkdir(lua_file_dir) lua_file_path = lua_dir + os.sep + lua_file_name + os.sep + lua_file_name + ".lua" else: lua_file_path = lua_dir + os.sep + name[:name.find(EXCEL_SUFFIX)] + ".lua" is_language = name == "LanguageDesc.xlsx" #判断是否跳过导表 if is_skip_export(excel_file_path, lua_file_path): #print(name," skip export") continue print(name) change_file_list.append(excel_file_path) change_file_list.append(lua_file_path) #如果使用多进程 if is_use_multi_thread: pool.apply_async(thread_func, (name, excel_file_path, lua_file_path, is_language)) else: excel_to_lua_file(excel_file_path, lua_file_path, is_language) else: for name in export_paths: if name.endswith(EXCEL_SUFFIX) and not name.startswith(TEMP_EXCEL_PREFIX): #print(name) excel_file_path = file_path + os.sep + name lua_file_name = name[:name.find(EXCEL_SUFFIX)] lua_dir = GENERATE_LUA_DIR if name == "LanguageDesc.xlsx": lua_dir = GENERATE_LUA_LANG_DIR if lua_file_name in alone_save_excel: print("need alone save",name) lua_file_dir = lua_dir + os.sep + lua_file_name if not os.path.exists(lua_file_dir): os.mkdir(lua_file_dir) lua_file_path = lua_dir + os.sep + lua_file_name + os.sep + lua_file_name + ".lua" else: lua_file_path = lua_dir + os.sep + name[:name.find(EXCEL_SUFFIX)] + ".lua" is_language = name == "LanguageDesc.xlsx" #判断是否跳过导表 if is_skip_export(excel_file_path, lua_file_path): #print(name," skip export") continue print(name) change_file_list.append(excel_file_path) change_file_list.append(lua_file_path) #如果使用多进程 if is_use_multi_thread: pool.apply_async(thread_func, (name, excel_file_path, lua_file_path, is_language)) else: excel_to_lua_file(excel_file_path, lua_file_path, is_language) if __name__ == "__main__": #开始计时 start_time = time.time() if not os.path.exists(GENERATE_LUA_DIR): os.makedirs(GENERATE_LUA_DIR) #读取md5文件 md5_file_dict = read_md5_file() clean_old_file = True export_all_file = True config_file_name = excel_config_file change_region_dir="" #需要替换的区域目录 export_paths="" if (len(sys.argv) > 1 and sys.argv[1] == "self" ): config_file_name = excel_self_config_file clean_old_file = False elif (len(sys.argv) > 1 and sys.argv[1] == "quickExport"): #快速导表 is_fast_export = True is_use_multi_thread = True elif (len(sys.argv) > 2 and sys.argv[1] == "region"): is_use_multi_thread = True change_region_dir=sys.argv[2] #需要替换的区域目录 elif (len(sys.argv) > 1 and sys.argv[1] != "self"): export_all_file = False clean_old_file = True export_paths = sys.argv[1:] if is_fast_export: clean_old_file = False if clean_old_file: if not export_all_file: print("export_paths = ", export_paths[0]) for file_name in os.listdir(GENERATE_LUA_DIR): if not file_name.startswith("SpellShowDesc") and not file_name.startswith("QuestEventDesc") and not file_name.startswith("DramaDesc") and not file_name.startswith("WorldDesc"): if file_name.replace(".lua", ".xlsx") in export_paths or file_name.replace(".meta", ".xlsx") in export_paths : print("clear_old_file:", file_name) file_path = os.path.join(GENERATE_LUA_DIR, file_name) if os.path.isdir(file_path): shutil.rmtree(file_path) else: os.remove(file_path) else: for file_name in os.listdir(GENERATE_LUA_DIR): if not file_name.startswith("SpellShowDesc") and not file_name.startswith("QuestEventDesc") and not file_name.startswith("DramaDesc") and not file_name.startswith("WorldDesc"): file_path = os.path.join(GENERATE_LUA_DIR, file_name) if os.path.isdir(file_path): shutil.rmtree(file_path) else: os.remove(file_path) if not os.path.exists(GENERATE_LUA_LANG_DIR): os.makedirs(GENERATE_LUA_LANG_DIR) config_file = open(config_file_name, 'r', encoding='utf-8') configs = json.load(config_file) # print(configs) config_file.close() # 多线程执行函数 #定义进程池 pool = multiprocessing.Pool(processes=6) #改动文件列表 change_file_list = [] #默认目录xls文件转lua文件 excel_list_to_lua_file(EXCEL_DIR, configs, export_all_file, export_paths, is_use_multi_thread, pool, change_file_list) #区域目录xls文件覆盖,重新生成lua文件 if change_region_dir != "" : region_file_path = EXCEL_DIR + os.sep + change_region_dir if not os.path.exists(region_file_path): print(region_file_path+" 目录不存在") exit(-1) print(region_file_path+" 目录文件进行覆盖处理") excel_list_to_lua_file(region_file_path, configs, export_all_file, export_paths, is_use_multi_thread, pool, change_file_list) #等待所有任务完成 pool.close() pool.join() #更新改动文件的MD5 for file in change_file_list: md5_file_dict[file] = get_md5(file) #写入md5文件 write_md5_file(md5_file_dict) #结束计时 end_time = time.time() #打印耗时,保留两位小数 #打印是否使用线程 print("is_use_multi_thread:", is_use_multi_thread) print("export excel to lua finish , time:", round(end_time - start_time, 2), "s")