// Excel2Json.js // const fs = require('fs'); const path = require('path'); const nodeXlsx = require('node-xlsx'); const typeFile = 'D:\\Jenkins\\fishClient\\fishClient\\config.d.ts' let FileTarget = { General: 0, Client: 1, Server: 2 }; let MainRun = { sourceFile: process.argv[2], clientFile: process.argv[3], serverFile: process.argv[4], options: process.argv[5], sourceDir: '', typeMap: {}, start() { // 非绝对路径则加上当前目录 if (this.sourceFile && !path.isAbsolute(this.sourceFile)) { this.sourceFile = path.join(__dirname, this.sourceFile); } if (this.clientFile && !path.isAbsolute(this.clientFile)) { this.clientFile = path.join(__dirname, this.clientFile); } if (this.serverFile && !path.isAbsolute(this.serverFile)) { this.serverFile = path.join(__dirname, this.serverFile); } if (!this.options || this.options != '-add') { this.delAllFiles(this.clientFile); this.delAllFiles(this.serverFile); } this.traversalDir(this.sourceFile, this.clientFile, this.serverFile); }, traversalDir(srcDir, clientDir, serverDir) { if (!srcDir || !fs.existsSync(srcDir)) { console.error("invalid srcDir=" + srcDir); return; } this.sourceDir = srcDir; let files = fs.readdirSync(srcDir); for (let i = 0, len = files.length; i < len; i++) { let file = files[i]; if (file.indexOf('~$') === 0 || (file.indexOf('.xlsx') < 0 && file.indexOf('.xlsm') < 0)) { continue; // 排除无效文件 } let curPath = path.join(srcDir, file); let stats = fs.statSync(curPath); if (stats.isDirectory()) { this.traversalDir(curPath, clientDir, serverDir); continue; } this.readExcelFile(curPath, clientDir, serverDir); } if (fs.existsSync(typeFile)) { let str = JSON.stringify(this.typeMap) str = str.replace('{', 'declare type ') str = str.replace(/-/g, '') str = str.replace(/"/g, '') str = str.replace(/(int)/g, 'number') str = str.replace(/(float)/g, 'number') str = str.replace(/},/g, '}\ndeclare type ') str = str.replace(/(:{)/g, ' = {') str = str.substr(0, str.length - 1) fs.writeFileSync(typeFile, str) } }, // 一个Excel文件可能包含N张表 readExcelFile(srcFile, clientDir, serverDir) { if (!srcFile || !fs.existsSync(srcFile)) { console.error('readExcelFile: invalid file path'); return; } let sheetArr = nodeXlsx.parse(srcFile); for (let i = 0, len = sheetArr.length; i < len; i++) { let sheet = sheetArr[i]; let sheetName = this.getSheetName(sheet); if (!sheetName || sheetName.length <= 0) { console.error('sheet is invalid. name=' + sheet.name); continue; } try { let outData = {}; let csArr = this.lowerArray(sheet.data[0]); let typeArr = this.lowerArray(sheet.data[1]); let keyArr = sheet.data[2]; if (csArr && csArr[0] === 'key-value') { // key-value表 outData = this.jsonObjectFromSheet(sheet, csArr, typeArr, keyArr); } else if (csArr && csArr[0] === 'language') { // 多语言js文件特殊处理 let content = this.i18nJsFromSheet(sheet, csArr, typeArr, keyArr); let fullPath = path.join(this.sourceDir, 'zh.js'); if (fullPath && content && content.length > 0) { this.writeFile(fullPath, content); } continue; } else { // 普通表 outData = this.jsonArrayFromSheet(sheet, csArr, typeArr, keyArr); } // 输出客户端配置文件 if (!!clientDir && clientDir.length > 0) { let clientFile = path.join(clientDir, sheetName); if (outData && outData.clientStr && outData.clientStr.length > 0) { let outStr = outData.clientStr.replace(/\\\\n/g, "\\n"); this.writeFile(clientFile, outStr); } } // 输出服务器配置文件 if (!!serverDir && serverDir.length > 0) { let serverFile = path.join(serverDir, sheetName); if (outData && outData.serverStr && outData.serverStr.length > 0) { this.writeFile(serverFile, outData.serverStr); } } } catch (err) { console.error('error to convert sheet, name=' + sheet.name + ', err=' + err.message); } } }, // 返回Array格式的字符串 jsonArrayFromSheet(sheet, csArr, typeArr, keyArr) { if (!sheet || !csArr || !typeArr || !keyArr) { return null; } let clientArr = []; let serverArr = []; let objKey = this.getSheetName(sheet).replace('Conf', '').replace('.json', '') // console.log("-------------sheet----------" + objKey) let obj = {} let typesArr = sheet.data[0] let valueTypeArr = sheet.data[1] let keysArr = sheet.data[2] keysArr[0] = keysArr[0].toLowerCase() for (let i = 0; i < typeArr.length; i++) { let type = typesArr[i] if ('c' == type || 'cs' == type) { obj[keysArr[i]] = valueTypeArr[i] this.typeMap[objKey] = obj } } for (let i = 4, len = sheet.data.length; i < len; i++) { let data = sheet.data[i]; if (data.length <= 0 || data[0] == undefined) { break; // 碰到空行则直接跳出 } let clientData = {}; let serverData = {}; for (let j = 0, len2 = csArr.length; j < len2; j++) { if (!this.checkColumnIsValid(csArr[j], typeArr[j], keyArr[j])) { break; } // 第一列表头转换为小写(兼容以前格式) let keyName = keyArr[j]; if (j === 0) { keyName = keyName.toLowerCase(); } let value = this.formatValueByType(data[j], typeArr[j]); let target = this.getFileTarget(csArr[j]); if (FileTarget.Client === target) { clientData[keyName] = value; } else if (FileTarget.Server === target) { serverData[keyName] = value; } else { clientData[keyName] = value; serverData[keyName] = value; } } let keys = Object.keys(clientData); if (keys.length > 0) { clientArr.push(clientData); } keys = Object.keys(serverData); if (keys.length > 0) { serverArr.push(serverData); } } let clientStr = ''; let serverStr = ''; if (clientArr && clientArr.length > 0) { clientStr = JSON.stringify(clientArr, null, '\t'); // 格式化Json输出 } if (serverArr && serverArr.length > 0) { serverStr = JSON.stringify(serverArr, null, '\t'); // 格式化Json输出 } return { clientStr, serverStr }; }, // 返回Object格式的字符串 jsonObjectFromSheet(sheet, csArr, typeArr, keyArr) { if (!sheet || !csArr || !typeArr || !keyArr) { return null; } let clientObj = {}; let serverObj = {}; // 如果指定了特殊key,则取指定列,否则默认取第1列 let langIndex = 1; if (this.options && this.options.length > 0) { let data = sheet.data[2]; for (let line = 0; line < 10; line++) { // 只遍历前10列 if (data[line] && data[line] == this.options) { langIndex = line; break; } } } for (let i = 4, len = sheet.data.length; i < len; i++) { let data = sheet.data[i]; if (data.length <= 0 || !data[0]) { break; // 碰到空行则直接跳出 } let keyName = data[0]; let value = this.formatValueByType(data[langIndex], typeArr[1]); let target = this.getFileTarget(csArr[1]); if (FileTarget.Client === target) { clientObj[keyName] = value; } else if (FileTarget.Server === target) { serverObj[keyName] = value; } else { clientObj[keyName] = value; serverObj[keyName] = value; } } let clientStr = ''; let serverStr = ''; let keys = Object.keys(clientObj); if (keys.length > 0) { clientStr = JSON.stringify(clientObj, null, '\t'); // 格式化Json输出 } keys = Object.keys(serverObj); if (keys.length > 0) { serverStr = JSON.stringify(serverObj, null, '\t'); // 格式化Json输出 } return { clientStr, serverStr }; }, // 返回i18n标准的js代码字符串 i18nJsFromSheet(sheet, csArr, typeArr, keyArr) { let outData = this.jsonObjectFromSheet(sheet, csArr, typeArr, keyArr); let str = ''; if (outData && outData.clientStr && outData.clientStr.length > 0) { let outStr = outData.clientStr.replace(/\\\\n/g, "\\n"); str = `if (!window.i18n) {window.i18n = {};} if (!window.i18n.languages) {window.i18n.languages = {};} window.i18n.languages['zh'] = ` + outStr + ';'; } return str; }, writeFile(fullPath, content) { if (!fullPath || !content) { console.error('writeFile: invalid params, path=' + fullPath); return; } fs.writeFile(fullPath, content, (err) => { if (err) { console.error('writeFile error: ' + err); return; } // console.log(content); //console.log('Success to write file: ' + fullPath); }); }, // 删除目录下所有文件(不包含子目录) delAllFiles(dir) { if (dir && fs.existsSync(dir)) { let files = fs.readdirSync(dir); files.forEach((file, index) => { let curPath = path.join(dir, file); if (!fs.statSync(curPath).isDirectory()) { fs.unlinkSync(curPath); } }); } }, // 获取有效数组并转换为小写 lowerArray(data) { let arr = []; if (!data || data.length <= 0) { return arr; } for (let i = 0, len = data.length; i < len; i++) { let str = data[i]; if (!str || str.length <= 0) { break; // 碰到空字符串代表之后的列都不需要,所以直接返回 } arr.push(str.toLowerCase()); } return arr; }, getFileTarget(cs) { let ret = FileTarget.General; if (cs === 'c') { ret = FileTarget.Client; } else if (cs === 's') { ret = FileTarget.Server; } return ret; }, checkColumnIsValid(cs, type, key) { if (!cs || cs.length <= 0) { return false; } if (cs !== 'cs' && cs !== 'c' && cs !== 's') { return false; } if (!type || type.length <= 0) { return false; } if (key == undefined || key.length <= 0) { return false; } return true; }, getSheetName(sheet) { let name = ''; if (!sheet.name || sheet.name.length <= 0 || !sheet.data || sheet.data.length <= 0) { return name; } let arr = sheet.name.split('|'); if (!arr || arr.length !== 2) { return name; } name = 'Conf' + arr[1] + '.json'; return name; }, formatValueByType(value, type) { let ret = null; switch (type) { case 'string': case 'int[]': ret = (value != undefined) ? String(value) : ''; break; case 'int': ret = (value != undefined) ? parseInt(value) : 0; break; case 'float': ret = (value != undefined) ? parseFloat(value) : 0; ret = this.keepFloat8(ret); break; default: ret = null; break; } return ret; }, // 保留小数点后8位,输入参数为浮点数 keepFloat8(num) { let ret = Math.round(num * 100000000) / 100000000; return ret; }, }; module.exports = MainRun; MainRun.start();