import { CodeSnippet, CSharpStringExtractor } from "./CSharpStringExtractor"; import { LiteralCollector } from "./LiteralCollector"; /** * 扫描C#代码, 扫描出其中所有形如 uictrl.text = 其他内容; 的代码片段, 其他内容可以是: $"{itemConfig.Name}"; 等等, 其他内容中形如 {变量} 的部分需要提取出来, 转换成 string.Format("{1}", colorStr, itemConfig.Name); 的格式 */ export class CSCodeScanner { // 正则表达式匹配 uictrl.text = $"..."; 的模式 private static readonly assignmentPattern = /([\w\.\u4e00-\u9fff]+\.text\s*\+?=(?![=>])\s*|return\s+)([^;]+)\s*;/mg; // 正则表达式匹配 xxx = "..."; 的模式 private static readonly assignmentPattern2 = /(^\s*[\.a-zA-Z0-9_\u4e00-\u9fff]+\s*\+?=)\s*;/mg; // 正则表达式匹配 匹配每个字符串 private static readonly stringPattern = /\$?"([^"]*)"/mg; // 正则表达式匹配 匹配获取成员值的表达式 private static getMemberValuePattern(trMethodName: string) { return new RegExp(`^(\\s*)([\\w\\.\\u4e00-\\u9fff\\[\\]]+)(\\.${trMethodName}\\(\\))?(\\s*)$`, "m"); } private static readonly memberSplit = /^[\s\+;]/; // 正则表达式匹配插值表达式 {...} private static readonly interpolationPattern = /\{([^}]+?)(\:[a-zA-Z\d-\.\#(?:\\\\:)\u4e00-\u9fff]+)?\}/mg; private static readonly stringFormatPattern = /(? 0) { // // 通过是否单行字符串并以 ".TR(); 结尾和判断是否内插字符串来判断是否需要在结尾附加 .TR() // let isSingleString = false; // if (stringMatchs.length == 1 && stringMatchs[0][0] == bodyLine) { // let stringMatchFirst = stringMatchs[0]; // let stringLine = stringMatchFirst[0]; // if (stringLine.startsWith("$") || stringLine.startsWith("@$")) { // let stringContent = stringMatchFirst[1]; // const variableMatches0 = [...stringContent.matchAll(CSCodeScanner.interpolationPattern)]; // isSingleString = variableMatches0.length == 0; // } else { // isSingleString = true; // } // } // if (isSingleString) { // let stringMatchFirst = stringMatchs[0]; // let stringLineIndex = stringMatchFirst.index; // let prefix = bodyLine.substring(0, stringLineIndex); // let stringLineFirst = stringMatchFirst[0]; // if (this.isNativeString(stringLineFirst)) { // convertedAssignLine = assignHead + prefix + stringMatchFirst[0] + ";"; // } else { // convertedAssignLine = assignHead + prefix + stringMatchFirst[0] + `.${trmethodname}();`; // } // } else { // let stringLines: string[] = []; // stringLines.push(assignHead); // let lastIndex = 0; // for (let i = 0; i < stringMatchs.length; i++) { // let stringMatch = stringMatchs[i]; // let stringLine = stringMatch[0]; // let stringContent = stringMatch[1]; // let stringLineIndex = stringMatch.index; // let stringStartIndexInContent = assignMatch.index + assignHead.length + stringMatch.index // /** // * 转换后的字符串 // */ // let convertedString: string; // if (stringLine.startsWith("$") || stringLine.startsWith("@$")) { // // 开始转换插值 // // 提取插值表达式中的变量 // const variableMatches0 = [...stringContent.matchAll(CSCodeScanner.interpolationPattern)]; // if (variableMatches0.length > 0) { // let variableMatches: RegExpExecArray[] = []; // let map = new Map(); // for (const variableMatche of variableMatches0) { // if (!map.has(variableMatche[1])) { // map.set(variableMatche[1], variableMatche); // variableMatches.push(variableMatche); // } // } // // 构建格式化字符串 // let formatString = stringContent; // variableMatches.forEach((variableMatche, index) => { // let varName = variableMatche[1]; // let suffix = variableMatche[2] ?? ""; // let replaced = formatString.replaceAll(`{${varName}${suffix}}`, `{${index}${suffix}}`); // if (replaced == formatString) { // let [lineNum, colNum] = CSCodeScanner.getLineIndexFromIndex(content, stringStartIndexInContent); // let tip = `可能无法处理的复杂情形1(${filePath}:${lineNum}:${colNum}): ${stringContent}`; // console.error(tip); // unexpects.push(tip); // } else { // formatString = replaced; // } // }); // // 构建string.Format调用 // let formatCall = `string.Format("${formatString}"`; // for (const variableMatche of variableMatches) { // let varName = variableMatche[1]; // formatCall += `, ${varName}`; // } // formatCall += ")"; // convertedString = formatCall; // literals.push(formatString); // } else { // // throw new Error("暂不支持插值字符串转换为 string.Format, 请手动处理"); // let isValidFormat = true; // if (stringContent.indexOf("{") != -1 || stringContent.indexOf("}") != -1) { // if (bodyLine.indexOf("{") != -1 && bodyLine.indexOf("}") != -1) { // let [lineNum, colNum] = CSCodeScanner.getLineIndexFromIndex(content, stringStartIndexInContent); // let tip = `可能无法处理的复杂情形2(${filePath}:${lineNum}:${colNum}): ${stringContent}`; // console.error(tip); // unexpects.push(tip); // isValidFormat = false; // } // } // let formattedStringLine = stringLine; // if (isValidFormat) { // // let stringStartIndex = stringMatch.index // // let stringEndIndex = stringStartIndex + stringLine.length // // let prefix = bodyLine.substring(stringStartIndex - 2, stringStartIndex) // // let suffix = bodyLine.substring(stringEndIndex, stringEndIndex + ".TR()".length) // // if ((stringStartIndex == 0 || prefix == "+ ") && suffix != ".TR()") { // // formattedStringLine = formattedStringLine + ".TR()" // // } // formattedStringLine = this.replaceStringsTR(stringMatch, stringLine, bodyLine, trmethodname); // literals.push(stringContent); // } // convertedString = formattedStringLine; // } // } else { // literals.push(stringContent); // convertedString = this.replaceStringsTR(stringMatch, stringLine, bodyLine, trmethodname); // } // // 串联字符串列表 // let prefix = bodyLine.substring(lastIndex, stringLineIndex); // lastIndex = stringLineIndex + stringLine.length; // stringLines.push(prefix); // stringLines.push(convertedString); // } // let matchEnd = stringMatchs[stringMatchs.length - 1]; // let endIndex = matchEnd.index + matchEnd[0].length; // stringLines.push(bodyLine.substring(endIndex)); // stringLines.push(";"); // convertedAssignLine = stringLines.join(""); // } // } else { // let convertBodyLine = CSCodeScanner.handleMembersConvert(bodyLine, assignHead, trmethodname) // if (convertBodyLine != null) { // convertedAssignLine = (assignHead ?? "") + convertBodyLine; // } else { // convertedAssignLine = assignLine // } // } // let convertedAssignLine2 = convertedAssignLine.replaceAll(this.stringFormatPattern, `${trclassname}.Format`); // snippets.push({ // originalIndex: assignMatch.index, // originalCode: assignLine, // convertedCode: convertedAssignLine2, // literals, // unexpects, // }); // } let capturer = new CSharpStringExtractor(); capturer.extractStrings(content, snippets, trclassname, trmethodname); return snippets; } public static filterSnippets(snippets: CodeSnippet[]) { let len = snippets.length; for (let i = len - 1; i >= 0; i--) { let snippet = snippets[i]; let needReplace = snippet.literals.some(literal => { const needTr = LiteralCollector.needTranslate(literal); return needTr; }); if (!needReplace) { snippets.splice(i, 1); } } } private static handleMembersConvert(bodyLine: string, assignHead: string, trmethodname: string) { let convertedAssignLine: string | null let memberLines = bodyLine.split("+"); if (assignHead != 'return ' && memberLines.length > 0) { for (let i = 0; i < memberLines.length; i++) { let memberLine = memberLines[i]; let regex = this.getMemberValuePattern(trmethodname); let match = memberLine.match(regex); if (match != null) { let value = match[2]; if (value != 'null' && value != 'true' && value != 'false' && !this.isNumericString(value)) { memberLines[i] = match[1] + match[2] + `.${trmethodname}()` + match[4]; } } } convertedAssignLine = memberLines.join("+") + ";"; } else { convertedAssignLine = null; } return convertedAssignLine; } static isNumericString(str: string) { if (typeof str !== 'string') return false; const num = Number(str); return Number.isFinite(num) && String(num) === str.trim(); // 防止 ' 123 ' → 误判(可选严格匹配) } public static replaceStringsTR(stringMatch: RegExpExecArray, stringLine: string, bodyLine: string, trmethodname: string): string { if (this.isNativeString(stringLine)) { return stringLine; } let trmethodcall = `.${trmethodname}()`; let formattedStringLine = stringLine; let stringStartIndex = stringMatch.index; let stringEndIndex = stringStartIndex + stringLine.length; let prefix = bodyLine.substring(stringStartIndex - 2, stringStartIndex); let suffix = bodyLine.substring(stringEndIndex, stringEndIndex + trmethodcall.length); if ((stringStartIndex == 0 || prefix == "+ " || prefix == ": ") && suffix != trmethodcall) { formattedStringLine = formattedStringLine + trmethodcall; } return formattedStringLine; } public static replaceInFile(content: string, snippets: CodeSnippet[]) { let parts: string[] = []; if (snippets.length > 0) { let lastIndex = 0; for (const snippet of snippets) { let part = content.substring(lastIndex, snippet.originalIndex); lastIndex = snippet.originalIndex + snippet.originalCode.length; parts.push(part); parts.push(snippet.convertedCode); } let endSnippet = snippets[snippets.length - 1]; let partEnd = content.substring(endSnippet.originalIndex + endSnippet.originalCode.length); parts.push(partEnd); } try{ let convertedContent = parts.join(""); return convertedContent; }catch(err){ console.error(`替换字符串失败: ${err}`); return content; } } }