using Luban.Common.Utils; using Luban.Job.Cfg.Datas; using Luban.Job.Cfg.DataSources.Excel; using Luban.Job.Cfg.RawDefs; using Luban.Job.Cfg.Utils; using Luban.Job.Common.Defs; using Luban.Job.Common.RawDefs; using Luban.Job.Common.Types; using Luban.Server.Common; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; namespace Luban.Job.Cfg.Defs { class CfgDefLoader : CommonDefLoader { private static readonly NLog.Logger s_logger = NLog.LogManager.GetCurrentClassLogger(); private readonly List _importExcelTableFiles = new(); private readonly List _importExcelEnumFiles = new(); private readonly List _importExcelBeanFiles = new(); private readonly List _branches = new(); private readonly List _cfgTables = new List
(); private readonly List _cfgServices = new List(); private readonly List _cfgGroups = new List(); private readonly List _defaultGroups = new List(); public CfgDefLoader(RemoteAgent agent) : base(agent) { RegisterRootDefineHandler("importexcel", AddImportExcel); RegisterRootDefineHandler("branch", AddBranch); RegisterRootDefineHandler("service", AddService); RegisterRootDefineHandler("group", AddGroup); RegisterModuleDefineHandler("table", AddTable); IsBeanFieldMustDefineId = false; } public Defines BuildDefines() { return new Defines() { TopModule = TopModule, Branches = _branches, Consts = this._consts, Enums = _enums, Beans = _beans, Tables = _cfgTables, Services = _cfgServices, Groups = _cfgGroups, }; } private static readonly List _excelImportRequireAttrs = new List { "name", "type" }; private void AddImportExcel(XElement e) { ValidAttrKeys(e, null, _excelImportRequireAttrs); var importName = XmlUtil.GetRequiredAttribute(e, "name"); if (string.IsNullOrWhiteSpace(importName)) { throw new Exception("importexcel 属性name不能为空"); } var type = XmlUtil.GetRequiredAttribute(e, "type"); if (string.IsNullOrWhiteSpace(type)) { throw new Exception($"importexcel name:'{importName}' type属性不能为空"); } switch (type) { case "table": this._importExcelTableFiles.Add(importName); break; case "enum": this._importExcelEnumFiles.Add(importName); break; case "bean": this._importExcelBeanFiles.Add(importName); break; default: throw new Exception($"importexcel name:'{importName}' type:'{type}' 不合法. 有效值为 table|enum|bean"); } } private static readonly List _branchRequireAttrs = new List { "name" }; private void AddBranch(XElement e) { ValidAttrKeys(e, null, _branchRequireAttrs); var branchName = e.Attribute("name").Value; if (string.IsNullOrWhiteSpace(branchName)) { throw new Exception("branch 属性name不能为空"); } if (this._branches.Any(b => b.Name == branchName)) { throw new Exception($"branch '{branchName}' 重复"); } _branches.Add(new Branch(branchName)); } private static readonly List _groupOptionalAttrs = new List { "default" }; private static readonly List _groupRequireAttrs = new List { "name" }; private void AddGroup(XElement e) { ValidAttrKeys(e, _groupOptionalAttrs, _groupRequireAttrs); List groupNames = CreateGroups(e.Attribute("name").Value); foreach (var g in groupNames) { if (_cfgGroups.Any(cg => cg.Names.Contains(g))) { throw new Exception($"group名:'{g}' 重复"); } } if (XmlUtil.GetOptionBoolAttribute(e, "default")) { this._defaultGroups.AddRange(groupNames); } _cfgGroups.Add(new Group() { Names = groupNames }); } private void FillValueValidator(CfgField f, string attrValue, string validatorName) { if (!string.IsNullOrWhiteSpace(attrValue)) { var validator = new Validator() { Type = validatorName, Rule = attrValue }; f.Validators.Add(validator); f.ValueValidators.Add(validator); } } private void FillValidators(string key, string attr, List result) { if (!string.IsNullOrWhiteSpace(attr)) { foreach (var validatorStr in attr.Split('#', StringSplitOptions.RemoveEmptyEntries)) { var sepIndex = validatorStr.IndexOf(':'); if (sepIndex < 0) { throw new Exception($"定义文件:{CurImportFile} 类型:'{CurDefine}' key:'{key}' attr:'{attr}' 不是合法的 validator 定义 (key1:value1#key2:value2 ...)"); } result.Add(new Validator() { Type = validatorStr[..sepIndex], Rule = validatorStr[(sepIndex + 1)..] }); } } } private readonly List _serviceAttrs = new List { "name", "manager", "group" }; private void AddService(XElement e) { var name = XmlUtil.GetRequiredAttribute(e, "name"); var manager = XmlUtil.GetRequiredAttribute(e, "manager"); List groups = CreateGroups(XmlUtil.GetOptionalAttribute(e, "group")); var refs = new List(); s_logger.Trace("service name:{name} manager:{manager}", name, manager); ValidAttrKeys(e, _serviceAttrs, _serviceAttrs); foreach (XElement ele in e.Elements()) { string tagName = ele.Name.LocalName; s_logger.Trace("service {service_name} tag: {name} {value}", name, tagName, ele); switch (tagName) { case "ref": { refs.Add(XmlUtil.GetRequiredAttribute(ele, "name")); break; } default: { throw new Exception($"service:'{name}' tag:'{tagName}' 非法"); } } } if (!ValidGroup(groups, out var invalidGroup)) { throw new Exception($"service:'{name}' group:'{invalidGroup}' 不存在"); } _cfgServices.Add(new Service() { Name = name, Manager = manager, Groups = groups, Refs = refs }); } private readonly Dictionary _name2CfgTable = new Dictionary(); private static List CreateGroups(string s) { return s.Split(',', ';').Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); } private bool ValidGroup(List groups, out string invalidGroup) { foreach (var g in groups) { if (!this._cfgGroups.Any(cg => cg.Names.Contains(g))) { invalidGroup = g; return false; } } invalidGroup = null; return true; } private ETableMode ConvertMode(string tableName, string modeStr, string indexStr) { ETableMode mode; switch (modeStr) { case "one": { if (!string.IsNullOrWhiteSpace(indexStr)) { throw new Exception($"定义文件:{CurImportFile} table:'{tableName}' mode=one 是单例表,不支持定义index属性"); } mode = ETableMode.ONE; break; } case "map": { //if ((string.IsNullOrWhiteSpace(indexStr) || indexStr.Split(',').Length != 1)) //{ // throw new Exception($"定义文件:{CurImportFile} table:{tableName} 是单键表,必须在index属性里指定1个key"); //} mode = ETableMode.MAP; break; } case "": { mode = ETableMode.MAP; break; } default: { throw new ArgumentException($"不支持的 mode:{modeStr}"); } } return mode; } private readonly List _tableOptionalAttrs = new List { "index", "mode", "group", "branch_input", "comment", "define_from_file" }; private readonly List _tableRequireAttrs = new List { "name", "value", "input" }; private void AddTable(XElement e) { ValidAttrKeys(e, _tableOptionalAttrs, _tableRequireAttrs); string name = XmlUtil.GetRequiredAttribute(e, "name"); string module = CurNamespace; string valueType = XmlUtil.GetRequiredAttribute(e, "value"); bool defineFromFile = XmlUtil.GetOptionBoolAttribute(e, "define_from_file"); string index = XmlUtil.GetOptionalAttribute(e, "index"); string group = XmlUtil.GetOptionalAttribute(e, "group"); string comment = XmlUtil.GetOptionalAttribute(e, "comment"); string input = XmlUtil.GetRequiredAttribute(e, "input"); string branchInput = XmlUtil.GetOptionalAttribute(e, "branch_input"); string mode = XmlUtil.GetOptionalAttribute(e, "mode"); AddTable(name, module, valueType, index, mode, group, comment, defineFromFile, input, branchInput); } private void AddTable(string name, string module, string valueType, string index, string mode, string group, string comment, bool defineFromExcel, string input, string branchInput) { var p = new Table() { Name = name, Namespace = module, ValueType = valueType, LoadDefineFromFile = defineFromExcel, Index = index, Groups = CreateGroups(group), Comment = comment, Mode = ConvertMode(name, mode, index), }; if (p.Groups.Count == 0) { p.Groups = this._defaultGroups; } else if (!ValidGroup(p.Groups, out var invalidGroup)) { throw new Exception($"定义文件:{CurImportFile} table:'{p.Name}' group:'{invalidGroup}' 不存在"); } p.InputFiles.AddRange(input.Split(',')); if (!string.IsNullOrWhiteSpace(branchInput)) { foreach (var subBranchStr in branchInput.Split('|').Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s))) { var nameAndDirs = subBranchStr.Split(':'); if (nameAndDirs.Length != 2) { throw new Exception($"定义文件:{CurImportFile} table:'{p.Name}' branch_input:'{subBranchStr}' 定义不合法"); } var branchDirs = nameAndDirs[1].Split(',', ';').ToList(); if (!p.BranchInputFiles.TryAdd(nameAndDirs[0], branchDirs)) { throw new Exception($"定义文件:{CurImportFile} table:'{p.Name}' branch_input:'{subBranchStr}' 子branch:'{nameAndDirs[0]}' 重复"); } } } AddTableList(p); } private void AddTableList(Table p) { if (!_name2CfgTable.TryAdd(p.Name, p)) { var exist = _name2CfgTable[p.Name]; throw new Exception($"定义文件:{CurImportFile} table:'{p.Namespace}.{p.Name}' 与 '{exist.Namespace}.{exist.Name}' 名称不能重复"); } _cfgTables.Add(p); } private async Task LoadTableRecordDefineFromFileAsync(Table table, string dataDir) { var inputFileInfos = await DataLoaderUtil.CollectInputFilesAsync(this.Agent, table.InputFiles, dataDir); var file = inputFileInfos[0]; var source = new ExcelDataSource(); var stream = new MemoryStream(await this.Agent.GetFromCacheOrReadAllBytesAsync(file.ActualFile, file.MD5)); var sheet = source.LoadFirstSheet(file.OriginFile, file.SheetName, stream); var cb = new CfgBean() { Namespace = table.Namespace, Name = table.ValueType, }; var rc = sheet.RowColumns; var attrRow = sheet.RowColumns[0]; var titleRow = sheet.RowColumns[1]; // 有可能没有注释行,此时使用标题行,这个是必须有的 var descRow = sheet.TitleRows >= 3 ? sheet.RowColumns[2] : titleRow; foreach (var f in sheet.RootFields) { var cf = new CfgField() { Name = f.Name, Id = 0 }; string[] attrs = (attrRow[f.FromIndex].Value?.ToString() ?? "").Trim().Split('&').Select(s => s.Trim()).ToArray(); if (attrs.Length == 0 || string.IsNullOrWhiteSpace(attrs[0])) { throw new Exception($"table:'{table.Name}' file:{file.OriginFile} title:'{f.Name}' type missing!"); } // 优先取desc行,如果为空,则取title行 cf.Comment = descRow[f.FromIndex].Value?.ToString(); if (string.IsNullOrWhiteSpace(cf.Comment)) { cf.Comment = titleRow[f.FromIndex].Value?.ToString(); } if (string.IsNullOrWhiteSpace(cf.Comment)) { cf.Comment = ""; } cf.Type = attrs[0]; for (int i = 1; i < attrs.Length; i++) { var pair = attrs[i].Split('='); if (pair.Length != 2) { throw new Exception($"table:'{table.Name}' file:{file.OriginFile} title:'{f.Name}' attr:'{attrs[i]}' is invalid!"); } var attrName = pair[0].Trim(); var attrValue = pair[1].Trim(); switch (attrName) { case "index": { cf.Index = attrValue; break; } case "sep": { cf.Sep = attrValue; break; } case "ref": case "path": case "range": { var validator = new Validator() { Type = attrName, Rule = attrValue }; cf.Validators.Add(validator); cf.ValueValidators.Add(validator); break; } case "multi_lines": { cf.IsMultiRow = attrValue == "1" || attrValue.Equals("true", StringComparison.OrdinalIgnoreCase); break; } case "group": { cf.Groups = attrValue.Split(',').Select(s => s.Trim()).Where(s => !string.IsNullOrWhiteSpace(s)).ToList(); break; } case "comment": { cf.Comment = attrValue; break; } case "convert": { cf.Converter = attrValue; break; } default: { throw new Exception($"table:'{table.Name}' file:{file.OriginFile} title:'{f.Name}' attr:'{attrs[i]}' is invalid!"); } } } cb.Fields.Add(cf); } return cb; } private async Task LoadTableRecordDefinesFromFileAsync(string dataDir) { var loadTasks = new List>(); foreach (var table in this._cfgTables.Where(t => t.LoadDefineFromFile)) { loadTasks.Add(Task.Run(async () => await this.LoadTableRecordDefineFromFileAsync(table, dataDir))); } foreach (var task in loadTasks) { this._beans.Add(await task); } } private async Task LoadTableListFromFileAsync(string dataDir) { if (this._importExcelTableFiles.Count == 0) { return; } var inputFileInfos = await DataLoaderUtil.CollectInputFilesAsync(this.Agent, this._importExcelTableFiles, dataDir); var defTableRecordType = new DefBean(new CfgBean() { Namespace = "__intern__", Name = "__TableRecord__", Parent = "", Alias = "", IsValueType = false, Sep = "", TypeId = 0, IsSerializeCompatible = false, Fields = new List { new CfgField() { Name = "full_name", Type = "string" }, new CfgField() { Name = "value_type", Type = "string" }, new CfgField() { Name = "index", Type = "string" }, new CfgField() { Name = "mode", Type = "string" }, new CfgField() { Name = "group", Type = "string" }, new CfgField() { Name = "comment", Type = "string" }, new CfgField() { Name = "define_from_excel", Type = "bool" }, new CfgField() { Name = "input", Type = "string" }, new CfgField() { Name = "branch_input", Type = "string" }, } }) { AssemblyBase = new DefAssembly("", null, true, Agent), }; defTableRecordType.PreCompile(); defTableRecordType.Compile(); defTableRecordType.PostCompile(); var tableRecordType = new TBean(defTableRecordType, false); foreach (var file in inputFileInfos) { var source = new ExcelDataSource(); var bytes = await this.Agent.GetFromCacheOrReadAllBytesAsync(file.ActualFile, file.MD5); var records = DataLoaderUtil.LoadCfgRecords(tableRecordType, file.OriginFile, null, bytes, true, false); foreach (var r in records) { DBean data = r.Data; //s_logger.Info("== read text:{}", r.Data); string fullName = (data.GetField("full_name") as DString).Value.Trim(); if (string.IsNullOrWhiteSpace(fullName)) { throw new Exception($"file:{file.ActualFile} 定义了一个空的table类名"); } string name = TypeUtil.GetName(fullName); string module = TypeUtil.GetNamespace(fullName); string valueType = (data.GetField("value_type") as DString).Value.Trim(); string index = (data.GetField("index") as DString).Value.Trim(); string mode = (data.GetField("mode") as DString).Value.Trim(); string group = (data.GetField("group") as DString).Value.Trim(); string comment = (data.GetField("comment") as DString).Value.Trim(); bool isDefineFromExcel = (data.GetField("define_from_excel") as DBool).Value; string inputFile = (data.GetField("input") as DString).Value.Trim(); string branchInput = (data.GetField("branch_input") as DString).Value.Trim(); AddTable(name, module, valueType, index, mode, group, comment, isDefineFromExcel, inputFile, branchInput); }; } } private async Task LoadEnumListFromFileAsync(string dataDir) { if (this._importExcelEnumFiles.Count == 0) { return; } var inputFileInfos = await DataLoaderUtil.CollectInputFilesAsync(this.Agent, this._importExcelEnumFiles, dataDir); var defTableRecordType = new DefBean(new CfgBean() { Namespace = "__intern__", Name = "__EnumInfo__", Parent = "", Alias = "", IsValueType = false, Sep = "", TypeId = 0, IsSerializeCompatible = false, Fields = new List { new CfgField() { Name = "full_name", Type = "string" }, new CfgField() { Name = "item", Type = "string" }, new CfgField() { Name = "alias", Type = "string" }, new CfgField() { Name = "value", Type = "string" }, new CfgField() { Name = "comment", Type = "string" }, } }) { AssemblyBase = new DefAssembly("", null, true, Agent), }; defTableRecordType.PreCompile(); defTableRecordType.Compile(); defTableRecordType.PostCompile(); var tableRecordType = new TBean(defTableRecordType, false); foreach (var file in inputFileInfos) { var source = new ExcelDataSource(); var bytes = await this.Agent.GetFromCacheOrReadAllBytesAsync(file.ActualFile, file.MD5); var records = DataLoaderUtil.LoadCfgRecords(tableRecordType, file.OriginFile, null, bytes, true, false); PEnum curEnum = null; foreach (var r in records) { DBean data = r.Data; //s_logger.Info("== read text:{}", r.Data); string fullName = (data.GetField("full_name") as DString).Value.Trim(); if (string.IsNullOrWhiteSpace(fullName)) { throw new Exception($"file:{file.ActualFile} 定义了一个空的enum类名"); } string name = TypeUtil.GetName(fullName); string module = TypeUtil.GetNamespace(fullName); if (curEnum == null || curEnum.Name != name || curEnum.Namespace != module) { curEnum = new PEnum() { Name = name, Namespace = module, IsFlags = false, Comment = "", IsUniqueItemId = true }; this._enums.Add(curEnum); } string item = (data.GetField("item") as DString).Value.Trim(); if (string.IsNullOrWhiteSpace(item)) { throw new Exception($"file:{file.ActualFile} module:'{module}' name:'{name}' 定义了一个空枚举项"); } string alias = (data.GetField("alias") as DString).Value.Trim(); string value = (data.GetField("value") as DString).Value.Trim(); string comment = (data.GetField("comment") as DString).Value.Trim(); curEnum.Items.Add(new EnumItem() { Name = item, Alias = alias, Value = value, Comment = comment }); }; } } private async Task LoadBeanListFromFileAsync(string dataDir) { if (this._importExcelBeanFiles.Count == 0) { return; } var inputFileInfos = await DataLoaderUtil.CollectInputFilesAsync(this.Agent, this._importExcelBeanFiles, dataDir); var ass = new DefAssembly("", null, true, Agent); var defBeanFieldType = new DefBean(new CfgBean() { Namespace = "__intern__", Name = "__FieldInfo__", Parent = "", Alias = "", IsValueType = false, Sep = "", TypeId = 0, IsSerializeCompatible = false, Fields = new List { new CfgField() { Name = "name", Type = "string" }, new CfgField() { Name = "type", Type = "string" }, new CfgField() { Name = "sep", Type = "string" }, new CfgField() { Name = "is_multi_rows", Type = "bool" }, new CfgField() { Name = "group", Type = "string" }, new CfgField() { Name = "reference", Type = "string" }, new CfgField() { Name = "path", Type = "string" }, new CfgField() { Name = "comment", Type = "string" }, } }) { AssemblyBase = ass, }; defBeanFieldType.PreCompile(); defBeanFieldType.Compile(); defBeanFieldType.PostCompile(); ass.AddType(defBeanFieldType); var defTableRecordType = new DefBean(new CfgBean() { Namespace = "__intern__", Name = "__BeanInfo__", Parent = "", Alias = "", IsValueType = false, Sep = "", TypeId = 0, IsSerializeCompatible = false, Fields = new List { new CfgField() { Name = "full_name", Type = "string" }, new CfgField() { Name = "sep", Type = "string" }, new CfgField() { Name = "comment", Type = "string" }, new CfgField() { Name = "fields", Type = "list,__FieldInfo__", IsMultiRow = true }, } }) { AssemblyBase = ass, }; ass.AddType(defTableRecordType); defTableRecordType.PreCompile(); defTableRecordType.Compile(); defTableRecordType.PostCompile(); ass.MarkMultiRows(); var tableRecordType = new TBean(defTableRecordType, false); foreach (var file in inputFileInfos) { var source = new ExcelDataSource(); var bytes = await this.Agent.GetFromCacheOrReadAllBytesAsync(file.ActualFile, file.MD5); var records = DataLoaderUtil.LoadCfgRecords(tableRecordType, file.OriginFile, null, bytes, true, false); foreach (var r in records) { DBean data = r.Data; //s_logger.Info("== read text:{}", r.Data); string fullName = (data.GetField("full_name") as DString).Value.Trim(); if (string.IsNullOrWhiteSpace(fullName)) { throw new Exception($"file:{file.ActualFile} 定义了一个空的bean类名"); } string name = TypeUtil.GetName(fullName); string module = TypeUtil.GetNamespace(fullName); if (string.IsNullOrWhiteSpace(name)) { throw new Exception($"file:{file.ActualFile} 定义了一个空bean类名"); } string sep = (data.GetField("sep") as DString).Value.Trim(); string comment = (data.GetField("comment") as DString).Value.Trim(); DList fields = data.GetField("fields") as DList; var curBean = new CfgBean() { Name = name, Namespace = module, Sep = sep, Comment = comment, Parent = "", Fields = fields.Datas.Select(d => (DBean)d).Select(b => this.CreateField( (b.GetField("name") as DString).Value.Trim(), (b.GetField("type") as DString).Value.Trim(), "", (b.GetField("sep") as DString).Value.Trim(), (b.GetField("is_multi_rows") as DBool).Value, (b.GetField("group") as DString).Value, "", "", (b.GetField("comment") as DString).Value.Trim(), (b.GetField("reference") as DString).Value.Trim(), (b.GetField("path") as DString).Value.Trim(), "", "", "", "" )).ToList(), }; this._beans.Add(curBean); }; } } public async Task LoadDefinesFromFileAsync(string dataDir) { await Task.WhenAll(LoadTableListFromFileAsync(dataDir), LoadEnumListFromFileAsync(dataDir), LoadBeanListFromFileAsync(dataDir)); await LoadTableRecordDefinesFromFileAsync(dataDir); } private static readonly List _fieldOptionalAttrs = new List { "index", "sep", "validator", "key_validator", "value_validator", "ref", "path", "range", "multi_rows", "group", "res", "convert", "comment" }; private static readonly List _fieldRequireAttrs = new List { "name", "type" }; protected override Field CreateField(XElement e) { ValidAttrKeys(e, _fieldOptionalAttrs, _fieldRequireAttrs); return CreateField(XmlUtil.GetRequiredAttribute(e, "name"), XmlUtil.GetRequiredAttribute(e, "type"), XmlUtil.GetOptionalAttribute(e, "index"), XmlUtil.GetOptionalAttribute(e, "sep"), XmlUtil.GetOptionBoolAttribute(e, "multi_rows"), XmlUtil.GetOptionalAttribute(e, "group"), XmlUtil.GetOptionalAttribute(e, "res"), XmlUtil.GetOptionalAttribute(e, "convert"), XmlUtil.GetOptionalAttribute(e, "comment"), XmlUtil.GetOptionalAttribute(e, "ref"), XmlUtil.GetOptionalAttribute(e, "path"), XmlUtil.GetOptionalAttribute(e, "range"), XmlUtil.GetOptionalAttribute(e, "key_validator"), XmlUtil.GetOptionalAttribute(e, "value_validator"), XmlUtil.GetOptionalAttribute(e, "validator") ); } private Field CreateField(string name, string type, string index, string sep, bool isMultiRow, string group, string resource, string converter, string comment, string refs, string path, string range, string keyValidator, string valueValidator, string validator) { var f = new CfgField() { Name = name, Index = index, Sep = sep, IsMultiRow = isMultiRow, Groups = CreateGroups(group), Resource = resource, Converter = converter, Comment = comment, }; // 字段与table的默认组不一样。 // table 默认只属于default=1的组 // 字段默认属于所有组 if (f.Groups.Count == 0) { } else if (!ValidGroup(f.Groups, out var invalidGroup)) { throw new Exception($"定义文件:{CurImportFile} field:'{name}' group:'{invalidGroup}' 不存在"); } f.Type = type; FillValueValidator(f, refs, "ref"); FillValueValidator(f, path, "path"); // (ue4|unity|normal|regex);xxx;xxx FillValueValidator(f, range, "range"); FillValidators("key_validator", keyValidator, f.KeyValidators); FillValidators("value_validator", valueValidator, f.ValueValidators); FillValidators("validator", validator, f.Validators); return f; } private static readonly List _beanOptinsAttrs = new List { "value_type", "alias", "sep", "comment" }; private static readonly List _beanRequireAttrs = new List { "name" }; protected override void AddBean(XElement e, string parent) { ValidAttrKeys(e, _beanOptinsAttrs, _beanRequireAttrs); var b = new CfgBean() { Name = XmlUtil.GetRequiredAttribute(e, "name"), Namespace = CurNamespace, Parent = parent.Length > 0 ? parent : "", TypeId = 0, IsSerializeCompatible = true, IsValueType = XmlUtil.GetOptionBoolAttribute(e, "value_type"), Alias = XmlUtil.GetOptionalAttribute(e, "alias"), Sep = XmlUtil.GetOptionalAttribute(e, "sep"), Comment = XmlUtil.GetOptionalAttribute(e, "comment"), }; var childBeans = new List(); bool defineAnyChildBean = false; foreach (XElement fe in e.Elements()) { switch (fe.Name.LocalName) { case "var": { if (defineAnyChildBean) { throw new LoadDefException($"定义文件:{CurImportFile} 类型:{b.FullName} 的多态子bean必须在所有成员字段 之前定义"); } b.Fields.Add(CreateField(fe)); ; break; } case "bean": { defineAnyChildBean = true; childBeans.Add(fe); break; } default: { throw new LoadDefException($"定义文件:{CurImportFile} 类型:{b.FullName} 不支持 tag:{fe.Name}"); } } } s_logger.Trace("add bean:{@bean}", b); _beans.Add(b); var fullname = b.FullName; foreach (var cb in childBeans) { AddBean(cb, fullname); } } } }