Files
ExcelTool/ExcelTool/Parser/GenModels.cs
T
Oliver 0f32860526 refactor: improve SharedIdNames generation logic in GenModels.cs
- Refactored SharedIdNames generation to use a HashSet for collecting shared names
- Updated loop to iterate over KeyValuePair for clarity
- Added inline comments for better code understanding
- Simplified list initialization syntax
- Improved type annotations in foreach loops
2026-04-02 14:28:23 +08:00

329 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ExcelTool.Parser
{
public static class GenModels
{
/// <summary>
/// 生成对应的 C# Model 类
/// </summary>
public static bool GenCSharpModel(List<ParsedSheet> parsedSheets, string outputDir, string nameSpace = "")
{
try
{
foreach(ParsedSheet sheet in parsedSheets)
{
StringBuilder sb = new();
sb.Append($"/*\n * auto generated by tools(注意:千万不要手动修改本文件)\n * {sheet.SheetName}\n */\n");
sb.Append("using System;\nusing System.IO;\nusing System.Collections.Generic;\nusing System.Text;\n\n");
if (!string.IsNullOrEmpty(nameSpace))
sb.Append($"namespace {nameSpace}\n{{\n");
// ── 数据行类 ─────────────────────────────────────────────────
sb.Append("[Serializable]\n");
sb.Append($"public partial class {sheet.SheetName} : IBinarySerializable\n");
sb.Append("{\n");
foreach (var header in sheet.Headers)
AppendProperty(sb, header);
AppendDeserializeMethod(sb, sheet.Headers, sheet.SheetName);
AppendSerializeMethod(sb, sheet.Headers, sheet.SheetName);
sb.Append("}\n\n");
// ── Config 容器类 ─────────────────────────────────────────────
AppendConfigClass(sb, sheet.SheetName);
if (!string.IsNullOrEmpty(nameSpace))
sb.Append("}\n");
FileManager.WriteToFile(Path.Combine(outputDir, $"{sheet.SheetName}.cs"), sb.ToString());
// ── AudioObjectDefinitions 生成(仅针对 AudioObject 表) ─────────────────────
if (sheet.SheetName == "AudioObject")
{
GenerateAudioObjectDefinitions(parsedSheets, outputDir, nameSpace);
}
}
return true;
}
catch (Exception ex)
{
ex.ToString().WriteErrorLine();
return false;
}
}
// ──────────────────────────────────────────────────────────────────────────
// 属性声明
// ──────────────────────────────────────────────────────────────────────────
private static void AppendProperty(StringBuilder sb, TableExcelHeader header)
{
// XML 注释
sb.Append("\t/// <summary>\n");
var descLines = header.FieldDesc?.Replace("\r", "").Split('\n');
if (descLines != null)
foreach (var line in descLines)
sb.Append($"\t/// {line}\n");
sb.Append("\t/// </summary>\n");
var csType = ResolveCSharpType(header.FieldType);
sb.Append($"\tpublic {csType} {header.FieldName} {{ get; set; }}\n\n");
}
// ──────────────────────────────────────────────────────────────────────────
// DeSerialize 方法
// ──────────────────────────────────────────────────────────────────────────
private static void AppendDeserializeMethod(StringBuilder sb, List<TableExcelHeader> headers, string sheetName)
{
sb.Append("\n\tpublic void DeSerialize(BinaryReader reader)\n\t{\n");
foreach (var header in headers)
{
var type = header.FieldType.ToLower();
var name = header.FieldName;
// 注册表中的具名类型
var desc = TypeRegistry.Get(type);
if (desc != null)
{
sb.Append(desc.GenDeserialize(name));
continue;
}
// list<T> 泛型语法
if (TryParseGenericList(type, out var elemType))
{
var elemDesc = TypeRegistry.Get(elemType);
if (elemDesc == null)
{
$"list<T> 的元素类型 \"{elemType}\" 未注册 ({sheetName})".WriteErrorLine();
continue;
}
sb.Append(TypeRegistry.GenListDeserialize(name, elemDesc.CSharpType, GetReadExpr(elemDesc)));
continue;
}
// 兜底:当枚举处理,类型名直接用 FieldType 原值(保留大小写)
sb.Append($"\t\t{name} = ({header.FieldType})reader.ReadByte();\n");
}
sb.Append("\t}\n");
}
// ──────────────────────────────────────────────────────────────────────────
// Serialize 方法
// ──────────────────────────────────────────────────────────────────────────
private static void AppendSerializeMethod(StringBuilder sb, List<TableExcelHeader> headers, string sheetName)
{
sb.Append("\n\tpublic void Serialize(BinaryWriter writer)\n\t{\n");
foreach (var header in headers)
{
var type = header.FieldType.ToLower();
var name = header.FieldName;
// 注册表
var desc = TypeRegistry.Get(type);
if (desc != null)
{
sb.Append(desc.GenSerialize(name));
continue;
}
// list<T> 泛型语法
if (TryParseGenericList(type, out _))
{
sb.Append(TypeRegistry.GenListSerialize(name));
continue;
}
// 兜底:当枚举处理
sb.Append($"\t\twriter.Write((byte){name});\n");
}
sb.Append("\t}\n");
}
// ──────────────────────────────────────────────────────────────────────────
// Config 容器类(不涉及类型系统,逻辑不变,仅格式整理)
// ──────────────────────────────────────────────────────────────────────────
private static void AppendConfigClass(StringBuilder sb, string name)
{
var camel = name.ToCamelCase();
sb.Append("[Serializable]\n");
sb.Append($"public partial class {name}Config : IBinarySerializable\n{{\n");
sb.Append($"\tDictionary<uint,{name}> m_{camel}Infos = new();\n");
sb.Append($"\tList<{name}> m_{camel}InfoList;\n\n");
sb.Append($"\tpublic List<{name}> {name}List()\n\t{{\n");
sb.Append($"\t\tthis.m_{camel}InfoList ??= new List<{name}>(this.m_{camel}Infos.Values);\n");
sb.Append($"\t\treturn this.m_{camel}InfoList;\n\t}}\n\n");
sb.Append($"\tpublic void DeSerialize(BinaryReader reader)\n\t{{\n");
sb.Append($"\t\tint count = reader.ReadInt32();\n");
sb.Append($"\t\tfor (int i = 0; i < count; i++)\n\t\t{{\n");
sb.Append($"\t\t\t{name} tempData = new();\n");
sb.Append($"\t\t\ttempData.DeSerialize(reader);\n");
sb.Append($"\t\t\tthis.m_{camel}Infos.Add(tempData.Id, tempData);\n");
sb.Append("\t\t}\n\t}\n\n");
sb.Append($"\tpublic void Serialize(BinaryWriter writer)\n\t{{\n");
sb.Append($"\t\twriter.Write(this.m_{camel}Infos.Count);\n");
sb.Append($"\t\tforeach ({name} {camel} in this.m_{camel}Infos.Values)\n\t\t{{\n");
sb.Append($"\t\t\t{camel}.Serialize(writer);\n");
sb.Append("\t\t}\n\t}\n\n");
sb.Append($"\tpublic {name} QueryById(uint id)\n\t{{\n");
sb.Append($"\t\treturn this.m_{camel}Infos.GetValueOrDefault(id);\n");
sb.Append("\t}\n}\n");
}
private static void GenerateAudioObjectDefinitions(List<ParsedSheet> parsedSheets, string outputDir, string nameSpace)
{
ParsedSheet audioSheet = parsedSheets.Find(s => s.SheetName == "AudioObject");
if (audioSheet == null) return;
Dictionary<uint, List<string>> idToNames = new();
Dictionary<string, List<uint>> nameToIds = new();
int idIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "Id");
int namesIndex = audioSheet.Headers.FindIndex(h => h.FieldName == "Name");
foreach (TableExcelRow row in audioSheet.Data.Rows)
{
// 根据 Headers 找到列索引
if (idIndex < 0 || namesIndex < 0) continue;
if (string.IsNullOrEmpty(row.StrList[idIndex])) continue;
uint id = Convert.ToUInt32(row.StrList[idIndex]);
// Names 是用分隔符(例如 ',')拼接的字符串
string rawNames = row.StrList[namesIndex];
List<string> names = new(rawNames.Split([','], StringSplitOptions.RemoveEmptyEntries));
idToNames[id] = names; // 同一个ID对应了多少个不同的名字(Container)
foreach (string name in names)
{
if (!nameToIds.TryGetValue(name, out List<uint> list))
{
list = [];
nameToIds[name] = list;
}
list.Add(id);
}
}
StringBuilder sb = new();
sb.Append("/* auto generated, do not modify */\n");
sb.Append("using System.Collections.Generic;\n\n");
if (!string.IsNullOrEmpty(nameSpace))
sb.Append($"namespace {nameSpace}\n{{\n");
sb.Append("public static class AudioObjectDefinitions\n{\n");
// NameToId
sb.Append("\tpublic static readonly Dictionary<string, uint> NameToId = new()\n\t{\n");
foreach (var kv in nameToIds)
{
uint minId = uint.MaxValue;
foreach (uint id in kv.Value)
if (id < minId) minId = id;
sb.Append($"\t\t{{ \"{kv.Key}\", {minId} }},\n");
}
sb.Append("\t};\n\n");
// AmbiguousNames
sb.Append("\tpublic static readonly HashSet<string> AmbiguousNames = new()\n\t{\n");
foreach (var kv in nameToIds)
{
if (kv.Value.Count > 1)
sb.Append($"\t\t\"{kv.Key}\",\n");
}
sb.Append("\t};\n\n");
// SharedIdNames
HashSet<string> sharedNames = [];
foreach (KeyValuePair<uint, List<string>> keyValuePair in idToNames)
{
if (keyValuePair.Value.Count > 1)
{
foreach (string name in keyValuePair.Value)
sharedNames.Add(name);
}
}
sb.Append("\tpublic static readonly HashSet<string> SharedIdNames = new()\n\t{\n");
foreach (string name in sharedNames)
{
sb.Append($"\t\t\"{name}\",\n");
}
sb.Append("\t};\n");
sb.Append("}\n");
if (!string.IsNullOrEmpty(nameSpace))
sb.Append("}\n");
FileManager.WriteToFile(Path.Combine(outputDir, "AudioObjectDefinitions.cs"), sb.ToString());
}
// ──────────────────────────────────────────────────────────────────────────
// 工具方法
// ──────────────────────────────────────────────────────────────────────────
/// <summary>把字段类型名解析为 C# 属性类型字符串</summary>
private static string ResolveCSharpType(string fieldType)
{
var lower = fieldType.ToLower();
var desc = TypeRegistry.Get(lower);
if (desc != null)
return desc.CSharpType;
// list<T> 泛型
if (TryParseGenericList(lower, out var elemType))
{
var elemDesc = TypeRegistry.Get(elemType);
return elemDesc != null ? $"List<{elemDesc.CSharpType}>" : $"List<{elemType}>";
}
// 兜底:当枚举处理,保留原始类型名大小写
return fieldType;
}
/// <summary>尝试解析 list&lt;T&gt; 语法,成功返回元素类型名</summary>
private static bool TryParseGenericList(string type, out string elemType)
{
if (type.StartsWith("list<") && type.EndsWith(">"))
{
elemType = type[5..^1];
return true;
}
elemType = null;
return false;
}
/// <summary>从 TypeDescriptor 反推 reader.ReadXxx() 表达式(用于 list&lt;T&gt; 代码生成)</summary>
static string GetReadExpr(TypeDescriptor desc)
{
return desc.ReadExpression ?? $"/* unknown read for {desc.TypeName} */";
}
}
}