fy-convertor
Version:
Convert excel/xml/json/bin/protocl/ts/as ...
677 lines • 27.3 kB
JavaScript
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(` { Macros.${e._attributes.value}}`);
}
if (e._attributes.cname) {
comments.push(` ${e._attributes.cname}`);
}
if (e._attributes.refer) {
comments.push(` { ${def._attributes.name}.${e._attributes.refer}}`);
}
if (e._attributes.count) {
comments.push(` Macros.${e._attributes.count}`);
}
if (e.isAtoi) {
comments.push(` 本字段仅为字符串索引,原始字符串转存至strings.json`);
}
if (e.isAtoiArr) {
comments.push(` 本字段由工具自动转为数组`);
}
if (e._attributes.safeprintf) {
comments.push(``);
}
if (e._attributes.sparse_array == '1') {
comments.push(``);
}
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