0f32860526
- 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
329 lines
15 KiB
C#
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<T> 语法,成功返回元素类型名</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<T> 代码生成)</summary>
|
|
static string GetReadExpr(TypeDescriptor desc)
|
|
{
|
|
return desc.ReadExpression ?? $"/* unknown read for {desc.TypeName} */";
|
|
}
|
|
}
|
|
}
|