UNPKG

fy-convertor

Version:

Convert excel/xml/json/bin/protocl/ts/as ...

677 lines 27.3 kB
import fs from 'fs-extra'; import path from 'path'; import xmljs from 'xml-js'; import { indent, makeComments, readXml } from '../tools/vendor.js'; import { cname_ClientUsed, isComplexType, isLiteralStringType, NumberLike, StringLike } from './def.js'; import { Alert } from '../tools/alert.js'; import { toolchain } from '../tools/toolchain.js'; import _ from 'lodash'; export class BaseStructParser { m_structDefMap = {}; m_cliStructs = []; m_structParentMap = {}; m_macroFileMap = {}; m_macroEntryMap = {}; convMap = {}; blacklist = []; errors = []; get rmFlashTail() { return true; } reset() { this.m_structDefMap = {}; this.m_cliStructs.length = 0; this.m_structParentMap = {}; this.m_macroFileMap = {}; this.m_macroEntryMap = {}; this.convMap = {}; this.blacklist.length = 0; this.errors.length = 0; } async readBlacklist(file, filter) { if (fs.existsSync(file)) { const blacklistContent = await fs.readFile(file, 'utf-8'); this.blacklist = blacklistContent.split(/\r?\n/); if (filter != null) this.blacklist = this.blacklist.filter(filter); } } structIgnored(structName) { if (this.blacklist.includes(structName)) return true; const parents = this.m_structParentMap[structName]; if (parents != null) { for (const p of parents) { if (!this.structIgnored(p)) return false; } return true; } return false; } entryIgnored(structName, entryName) { return this.blacklist.includes(structName + '::' + entryName); } async readStructFile(f, isAdditional) { // console.log(`reading struct from ${f}`); const fileContent = await readXml(f); let xml; try { xml = xmljs.xml2json(fileContent, { compact: true, spaces: 0, ignoreComment: true }); } catch (e) { console.error(e); console.log('xml parse failed:', f); await Alert.Instance.sendBuildFailureAlert(`xml文件格式错误: ${f.replaceAll('\\', '/')}\n${e instanceof Error ? e.message : String(e)}`); await Alert.Instance.alertErrorFile(f); process.exit(1); } const doc = JSON.parse(xml); if (doc.ConvList) { this.readConvertListDoc(f, doc); return; } const obj = doc; if (obj.metalib.struct) { if (obj.metalib.struct instanceof Array) { for (let m of obj.metalib.struct) { this.addStruct(m, isAdditional, f); } } else { this.addStruct(obj.metalib.struct, isAdditional, f); } } if (obj.metalib.macro) { if (obj.metalib.macro instanceof Array) { for (let m of obj.metalib.macro) { this.addMacro(m, f); } } else { this.addMacro(obj.metalib.macro, f); } } if (obj.metalib.macros) { if (obj.metalib.macros.macro instanceof Array) { for (let m of obj.metalib.macros.macro) { this.addMacro(m, f); } } else { this.addMacro(obj.metalib.macros.macro, f); } } } async checkErrors() { // 检查是否有类型结构未定义 for (const k in this.m_structDefMap) { const w = this.m_structDefMap[k]; for (const entry of w.def.entries) { const t = entry._attributes.type; if (isComplexType(t) && t != 'datetime' && t != 'date') { if (!this.m_structDefMap[t]) { this.errors.push({ msg: `未找到${k}:${entry._attributes.name}的类型定义${t},可能导致转表失败` }); } } } if (w.def._attributes.sortkey) { const sortEntry = w.def.entries.find(v => v._attributes.name == w.def._attributes.sortkey); if (sortEntry == null) { this.errors.push({ msg: `${k}的排序字段${w.def._attributes.sortkey}不存在,可能导致转表失败` }); } } } if (this.errors.length > 0) { this.errors.forEach((v) => console.error(v.msg)); await Alert.Instance.sendBuildFailureAlert(`xml错误: ${this.errors[0].msg.replaceAll('\\', '/')}`); if (this.errors[0].file) await Alert.Instance.alertErrorFile(this.errors[0].file); process.exit(1); } } readConvertListDoc(file, doc) { const cnodes = doc.ConvList.ConvTree.CommNode; const binMap = {}; for (const cnode of cnodes) { if (cnode.ResNode == null) continue; let nodes; if (cnode.ResNode instanceof Array) { nodes = cnode.ResNode; } else { nodes = [cnode.ResNode]; } for (const rnode of nodes) { const oldNode = binMap[rnode._attributes.BinFile]; if (oldNode) { this.errors.push({ msg: `BinFile定义冲突: ${oldNode._attributes.Name} vs ${rnode._attributes.Name}`, file }); } const struct = rnode._attributes.Meta.replace('_Flash', 'M'); this.convMap[struct] = { name: rnode._attributes.Name, meta: rnode._attributes.Meta, xlsx: rnode._attributes.ExcelFile, sheet: rnode._attributes.IncludeSheet, xml: rnode._attributes.EntryMapFile, bin: rnode._attributes.BinFile, sort: rnode._attributes.Sort }; binMap[rnode._attributes.BinFile] = rnode; } } } getConvInfo(structName) { return this.convMap[structName]; } addStruct(structDef, isAdditional, file) { let isCliStruct = this.isClientStruct(structDef); let n = structDef._attributes.name; structDef._attributes.__rawName__ = n; if (this.rmFlashTail) { structDef._attributes.name = n = n.replace('_Flash', 'M'); } let entries; if (structDef.entry instanceof Array) { entries = structDef.entry; } else { entries = structDef.entry ? [structDef.entry] : []; } const entryMap = {}; for (const entry of entries) { entryMap[entry._attributes.name] = entry; const options = entry._attributes.convertOption?.split(','); if (options) { entry.isNoJson = options.includes('noJson'); entry.isLang = options.includes('lang'); entry.isOptional = options.includes('?') && !toolchain.projCfg.xml2json.disableOptional; entry.isAtoi = options.includes('atoi'); entry.isAtoiArr = options.includes('atoi_arr'); entry.isUnique = options.includes('unique'); // validate(LE1000) const validateStrs = options.filter(v => v.startsWith('validate(')); for (const v of validateStrs) { const mch = v.match(/validate\(LE(\d+)\)/); if (mch) { entry.validates = entry.validates || []; entry.validates.push({ type: 'LE', value: Number(mch[1]), description: v }); } } } } for (const entry of entries) { // 检查是否有ref字段未定义(比如被误删) if (entry._attributes.refer && !entryMap[entry._attributes.refer]) { this.errors.push({ msg: `refer of ${n.replace(/M$/, '_Flash')}::${entry._attributes.name} missed: ${entry._attributes.refer}`, file }); } // 检查string类型是否有定义size if (isLiteralStringType(entry._attributes.type) && !entry._attributes.size) { this.errors.push({ msg: `string type should have a size attribute defined: ${n.replace(/M$/, '_Flash')}::${entry._attributes.name}`, file }); } } // 检查sort字段 if (structDef._attributes.sortkey != null && entryMap[structDef._attributes.sortkey] == null && file != 'AdditionalMember.xml') { this.errors.push({ msg: `sort key not exists!` + file + ', ' + n.replace(/M$/, '_Flash'), file }); } structDef.entries = entries; for (const e of entries) { if (e._attributes.value) { let arr = this.m_macroEntryMap[e._attributes.value]; if (arr == null) this.m_macroEntryMap[e._attributes.value] = arr = []; arr.push({ struct: structDef._attributes.name, entry: e }); } if (isComplexType(e._attributes.type)) { let arr = this.m_structParentMap[e._attributes.type]; if (arr == null) this.m_structParentMap[e._attributes.type] = arr = []; arr.push(structDef._attributes.name); } } let oldDef = this.m_structDefMap[n]; if (oldDef) { if (isAdditional) { for (let e of entries) { e.isAdditional = true; oldDef.def.entries.push(e); if (this.rmFlashTail) { e._attributes.type = e._attributes.type.replace('_Flash', 'M'); } if (isComplexType(e._attributes.type)) { if (isCliStruct && isComplexType(e._attributes.type)) { this.addCliStruct(e._attributes.type); } } } } else { console.warn(`[WARNING]Structure redefinition for ${n}: ${file}`); } } else { this.m_structDefMap[n] = { def: structDef, file: file }; if (isCliStruct) { this.m_cliStructs.push(n); for (let e of entries) { if (this.rmFlashTail) { e._attributes.type = e._attributes.type.replace('_Flash', 'M'); } if (isComplexType(e._attributes.type)) { this.addCliStruct(e._attributes.type); } } } } } isClientStruct(struct) { return true; } addCliStruct(structName) { if (!this.m_cliStructs.includes(structName)) { this.m_cliStructs.push(structName); } } getStructDef(structName) { let wrap = this.m_structDefMap[structName]; if (!wrap) { console.error('[ERROR] getStructDef: No struct definition!', structName); return null; } return wrap.def; } mergeStructs(a, b) { let sa = this.getStructDef(a); if (!sa) return; let sb = this.getStructDef(b); if (!sb) return; let ea = sa.entries; let eb = sb.entries; let ema = {}; let emb = {}; for (let e of ea) { ema[e._attributes.name] = e; } for (let e of eb) { emb[e._attributes.name] = e; } sa.mergedEntries = []; sb.mergedEntries = []; for (let e of ea) { if (!emb[e._attributes.name]) { sb.mergedEntries.push(e); } } for (let e of eb) { if (!ema[e._attributes.name]) { sa.mergedEntries.push(e); } } } makeStructCode(structName, type, descGenerator) { if (this.structIgnored(structName)) return ''; let wrap = this.m_structDefMap[structName]; if (!wrap) { console.error('[ERROR] makeStructCode: No struct definition!', structName); process.exit(1); } const def = wrap.def; // if (accurateUnion && def._attributes.class == 'union') { // return ''; // } let entries = def.entries; if (def.mergedEntries) { entries = entries.concat(def.mergedEntries); } // union字段处理 const { selectEntry, unionEntry } = this.getUnionInfo(structName); const isUnion = selectEntry && unionEntry; const isUnionReq = isUnion && structName.endsWith('_Request'); let code = '', declareStr; // request生成泛型映射 if (isUnionReq) { declareStr = `export ${type} ${structName}<T extends number>`; if (type == 'type') declareStr += ' ='; declareStr += ' '; const unionDef = this.getStructDef(unionEntry._attributes.type); // 需包含response的select const rspStructName = structName.replace('_Request', '_Response'); const rspUnionInfo = this.getUnionInfo(rspStructName); let sentries = unionDef.entries; if (rspUnionInfo.unionEntry) { const rspUnionDef = this.getStructDef(rspUnionInfo.unionEntry._attributes.type); sentries = sentries.concat(rspUnionDef.entries); } const rts = _.uniq(sentries.map(v => `Macros.${v._attributes.value}`)).join(' | '); code += `export type ${structName}Select = ${rts};\n`; const ueCodes = [], usedMap = {}; // Request自身的select映射 for (const ue of unionDef.entries) { usedMap[ue._attributes.value] = true; const md = this.getMacro(ue._attributes.value); if (md == null) { console.error('no macro denition found for: ' + ue._attributes.value + '@' + structName); } let ueCode = `// Macros.${ue._attributes.value}\n[${md._attributes.value}]: {\n`; ueCode += ` ${selectEntry._attributes.name}: Macros.${ue._attributes.value};\n`; const subUe = { _attributes: { desc: ue._attributes.desc, name: ue._attributes.name, type: ue._attributes.type } }; const subUeCode = this.makeBody(unionDef, [subUe]); ueCode += ` ${unionEntry._attributes.name}: ` + indent('{\n' + subUeCode + '\n}', ' ', 1) + '\n}'; ueCodes.push(ueCode); } // 补充Response的select映射 if (rspUnionInfo.unionEntry) { const rspUnionDef = this.getStructDef(rspUnionInfo.unionEntry._attributes.type); for (const ue of rspUnionDef.entries) { if (usedMap[ue._attributes.value]) continue; const md = this.getMacro(ue._attributes.value); let ueCode = `// Macros.${ue._attributes.value}\n[${md._attributes.value}]: {\n`; ueCode += ` ${selectEntry._attributes.name}: Macros.${ue._attributes.value};\n}`; ueCodes.push(ueCode); } } code += `interface _${structName}Map {\n`; code += indent(ueCodes.join(',\n'), ' '); code += '\n}\n'; } else { declareStr = `export ${type} ${structName + (type == 'type' ? ' =' : '')} `; } const comment = this.makeStructComment(structName, wrap); if (comment) { code += comment; } code += declareStr; // 普通字段 const normalEntries = isUnion ? entries.filter(v => v != selectEntry && v != unionEntry) : entries; const normalBody = this.makeBody(def, normalEntries, descGenerator); // union字段 if (isUnion) { if (isUnionReq) { if (normalBody) { code += '{\n'; code += normalBody + '\n} & ('; } code += `T extends ${structName}Select ? _${structName}Map[T] : { ${selectEntry._attributes.name}: T }`; if (normalBody) { code += ')'; } code += ';'; } else { const unionDef = this.getStructDef(unionEntry._attributes.type); const ueCodes = []; // 为兼容老代码,同时生成通用字段 let gCode = '{\n'; gCode += ` ${selectEntry._attributes.name}: number;\n`; gCode += ` ${unionEntry._attributes.name}: ${unionEntry._attributes.type};\n`; gCode += '\n}'; ueCodes.push(gCode); // 为每个select单独生成对应结构映射 for (const ue of unionDef.entries) { let ueCode = '{\n'; ueCode += ` ${selectEntry._attributes.name}: Macros.${ue._attributes.value};\n`; const subUe = { _attributes: { desc: ue._attributes.desc, name: ue._attributes.name, type: ue._attributes.type } }; const subUeCode = this.makeBody(unionDef, [subUe]); ueCode += ` ${unionEntry._attributes.name}: ` + indent('{\n' + subUeCode + '\n}', ' ', 1); ueCode += '\n}'; ueCodes.push(ueCode); } if (normalBody) { code += '{\n' + normalBody + '\n} & (\n' + indent(ueCodes.join(' | '), ' ') + '\n)'; } else { code += ueCodes.join(' | '); } } } else { code += '{\n' + normalBody + '\n}'; } return code + '\n'; } getUnionInfo(structName) { let wrap = this.m_structDefMap[structName]; if (!wrap) { return { selectEntry: null, unionEntry: null }; } const accurateUnion = toolchain.projCfg.xml2protocol?.accurateUnion == 'YES' && (structName.endsWith('_Request') || structName.endsWith('_Response') || structName.endsWith('_Notify')); if (!accurateUnion) return { selectEntry: null, unionEntry: null }; let selectEntry = null, unionEntry = null; for (let e of wrap.def.entries) { if (e._attributes.select) { unionEntry = e; break; } } if (unionEntry) { for (let e of wrap.def.entries) { if (e._attributes.name == unionEntry._attributes.select) { selectEntry = e; break; } } } return { selectEntry, unionEntry }; } makeBody(def, entries, descGenerator) { const isUnion = false; //def._attributes.class == 'union'; const codes = []; for (let e of entries) { if (e._attributes.cname === cname_ClientUsed) continue; if (e.isNoJson) continue; const optional = e.isOptional ? '?' : ''; const comments = []; if (e._attributes.desc) comments.push(e._attributes.desc); if (e._attributes.value) { // 增加union字段关联宏信息 comments.push(`@union {@link Macros.${e._attributes.value}}`); } if (e._attributes.cname) { comments.push(`@cname ${e._attributes.cname}`); } if (e._attributes.refer) { comments.push(`@refer {@link ${def._attributes.name}.${e._attributes.refer}}`); } if (e._attributes.count) { comments.push(`@count Macros.${e._attributes.count}`); } if (e.isAtoi) { comments.push(`@atoi 本字段仅为字符串索引,原始字符串转存至strings.json`); } if (e.isAtoiArr) { comments.push(`@atoi_arr 本字段由工具自动转为数组`); } if (e._attributes.safeprintf) { comments.push(`@safeprintf`); } if (e._attributes.sparse_array == '1') { comments.push(`@sparse_array`); } if (descGenerator) { const s = descGenerator(def, e); if (s?.length) { comments.push(...s); } } let ecode = ''; if (comments.length > 0) { const cstr = makeComments(comments); ecode += indent(cstr, ' ') + '\n'; } ecode += ` ${e._attributes.name}${optional}: ${this.getEntryType(e)};`; codes.push(ecode); } let code; if (isUnion) { code = codes.map(v => `{\n${v}\n}`).join(' | '); } else { code = codes.join('\n'); } return code; } makeStructComment(structName, wrap) { if (wrap.def._attributes.desc) { return `/**${wrap.def._attributes.desc}(defined in ${wrap.file})*/\n`; } return ''; } makeSendMsgCode(structName, msgIds) { if (this.structIgnored(structName)) return ''; let genericParam1 = '', genericParam2 = '', genericParam3 = '', paramStr, msgidStr; const { selectEntry, unionEntry } = this.getUnionInfo(structName); const isUnion = selectEntry && unionEntry; if (isUnion) { genericParam1 = `<T extends number>`; genericParam2 = '<T>'; genericParam3 = ` as unknown as Protocol.${structName}<T>`; } if (msgIds.length == 1) { paramStr = ''; msgidStr = ` const msgid = Macros.${msgIds[0]};`; } else { paramStr = 'msgid: ' + msgIds.map((v) => `Macros.${v}`).join(' | '); msgidStr = ''; } let code = `static get${structName}${genericParam1}(${paramStr}): Protocol.FyMsg<Protocol.${structName}${genericParam2}> {${msgidStr} SendMsgUtil._msg.m_stMsgHead.m_uiMsgID = msgid; let body: Protocol.${structName}${genericParam2} = SendMsgUtil._bodyObjects[msgid]; if (null == body) { body = ${this.makeStructInitCode(structName)}${genericParam3}; SendMsgUtil._bodyObjects[msgid] = body; } SendMsgUtil._msg.m_msgBody = body; return SendMsgUtil._msg; }\n`; return code; } makeStructInitCode(structName) { const wrap = this.m_structDefMap[structName]; if (!wrap) { console.error('[ERROR] makeStructInitCode: No struct definition!', structName); process.exit(1); } const def = wrap.def; let code = ''; for (const e of def.entries) { if (code) code += ', '; let einit; if (e._attributes.count) { einit = '[]'; } else { const type = e._attributes.type; if (NumberLike.includes(type)) { einit = '0'; } else if (StringLike.includes(type)) { einit = '\'\''; } else { einit = this.makeStructInitCode(type); } } code += `${e._attributes.name}: ${einit}`; } code = '{' + code + '}'; return code; } getEntryType(e) { let type = e._attributes.type; if (e.isAtoiArr) { type = 'number[]'; } else if (NumberLike.includes(type) || e.isAtoi) { type = 'number'; } else if (StringLike.includes(type)) { type = 'string'; } if (e._attributes.count || e._attributes.refer) { type += '[]'; } return type; } addMacro(macro, file) { const fileName = path.basename(file); let book = this.m_macroFileMap[fileName]; if (!book) this.m_macroFileMap[fileName] = book = { map: {}, list: [] }; let n = macro._attributes.name; let old = book.map[n]; if (old) { this.errors.push({ msg: `[ERROR]Macro redefinition for ${n} in ${file}: ${macro._attributes.value} vs old: ${old._attributes.value}`, file }); for (let i = book.list.length - 1; i >= 0; i--) { if (book.list[i]._attributes.name == n) { book.list.splice(i, 1); break; } } } book.list.push(macro); book.map[n] = macro; } getMacrosOfFile(fileName) { const out = this.m_macroFileMap[fileName].list; this.sortMacros(out); return out; } getAllMacros() { const out = []; for (const fileName in this.m_macroFileMap) { out.push(...this.m_macroFileMap[fileName].list); } this.sortMacros(out); return out; } getMacro(name, fileName) { if (fileName) { const mm = this.m_macroFileMap[fileName]; if (mm.map[name]) { return mm.map[name]; } } for (const fn in this.m_macroFileMap) { const mm = this.m_macroFileMap[fn]; if (mm.map[name]) { return mm.map[name]; } } return null; } sortMacros(arr) { const names = []; for (const m of arr) { names.push(m._attributes.name); } names.sort(); const indexMap = {}; for (let i = 0, len = names.length; i < len; i++) { indexMap[names[i]] = i; } arr.sort((a, b) => { return indexMap[a._attributes.name] - indexMap[b._attributes.name]; }); } getMacroRelatedEntry(macroName) { return this.m_macroEntryMap[macroName]; } } //# sourceMappingURL=BaseStructParser.js.map