using System.Data; using System.Text.RegularExpressions; namespace etoy { sealed class CommonTableCsvParser { // excel 上的规则 // 默认0列为主键(long/int) 0-3行按顺序表示:字段名、数据类型、数据标签(Tag)、字段描述, 正式数据从 4行开始 // * 0 1 2 3 4 .... 备注 // 0 #字段名 no name xxxx yyyy (‘#’开头忽略) // 1 #类型 int string repeated int repeated CustomType // 2 #Tags Tag1 Tag2 Tag1;Tag2;Tag3 Tag4 (';'分号隔开) // 3 #字段描述 主键 名字 XXXXXXX YYYYYY // 4 #正式数据 const char TAG_SEPARATOR = ';'; const char TAG_SEPARATOR_VALUE = ':'; const string FIELD_REPEATED = "[]"; public Table Parse(DataTable dt, string path) { //string tableDescription = dt.Rows[0][0].ToString().ToDescription(); TableTag tag = GetTableTag(dt, out string tableName, path); Dictionary fieldInfos = GetFieldInfos(dt, tableName); var table = new Table { Path = path, Name = tableName, Description = tableName, TableTag = tag, FieldInfos = fieldInfos.Values.ToArray() }; var rowOffset = 4; if (TryGetTableEnum(dt, fieldInfos, tableName, out var @enum)) { table.tableEnum = @enum; rowOffset = 5; } table.Rows = GetRows(dt, fieldInfos, rowOffset); return table; } Dictionary GetFieldInfos(DataTable dt, string tableName) { int columnCount = dt.Columns.Count; int rowOffset = 0; Dictionary fieldInfos = new Dictionary(); for (int i = 0; i < columnCount; i++) { string fieldName = dt.Rows[rowOffset][i].ToString().ToFieldName(); if (string.IsNullOrEmpty(fieldName)) break; if (fieldName.StartsWith("#")) continue; //const string REPEATED_STR = "repeated"; string fieldType = dt.Rows[rowOffset + 1][i].ToString().Trim(); bool isRepeaded = fieldType.Contains(FIELD_REPEATED); if (isRepeaded) fieldType = fieldType.Substring(0, fieldType.IndexOf(FIELD_REPEATED)).Trim(); string tags = dt.Rows[rowOffset + 2][i].ToString().ToTags(); string description = dt.Rows[rowOffset + 3][i].ToString().ToDescription(); FieldInfo field = new FieldInfo(); field.Column = i; field.FieldName = fieldName; field.FieldType = fieldType; field.IsRepeated = isRepeaded; field.Description = description; field.Tags = tags; field.TagInfos = GetFieldTagInfos(rowOffset + 2, i, tags, tableName); fieldInfos[i] = field; } return fieldInfos; } FieldTagInfo[] GetFieldTagInfos(int excelRow, int excelCol, string tags, string tableName) { if (string.IsNullOrEmpty(tags)) return new FieldTagInfo[0]; string[] fieldTags = tags.Split(TAG_SEPARATOR); if (fieldTags.Length > 0) { List result = new List(); for (int i = 0, length = fieldTags.Length; i < length; i++) { string[] kv = fieldTags[i].Split(TAG_SEPARATOR_VALUE); if (kv.Length == 1) { bool valid = Enum.TryParse(kv[0], true, out var fieldTag); if (!valid) { // tag字段只写了 'c'或's'或'sc' if (kv[0].Contains('c', StringComparison.OrdinalIgnoreCase) || kv[0].Contains('s', StringComparison.OrdinalIgnoreCase)){ result.Add(new FieldTagInfo() { Key = FieldTag.Output, Value = kv[0] }); } else throw new Exception($"配置表({tableName}) 不支持该Tag格式: [{tags}], 应满足以下格式: [Tag1Key:Tag1Value;Tag2Key:Tag2Value] (行: {excelRow}, 列: {excelCol})"); } else { FieldTagInfo info = new FieldTagInfo(); info.Key = fieldTag; info.Value = null; result.Add(info); } } else if (kv.Length == 2) { bool valid = Enum.TryParse(kv[0], true, out var fieldTag); if (!valid) throw new Exception($"配置表({tableName}) 不支持该Tag格式: [{tags}], 应满足以下格式: [Tag1Key:Tag1Value;Tag2Key:Tag2Value] (行: {excelRow}, 列: {excelCol})"); FieldTagInfo info = new FieldTagInfo(); info.Key = fieldTag; info.Value = kv[1]; result.Add(info); } else throw new Exception($"配置表({tableName}) 不支持该Tag格式: [{tags}], 应满足以下格式: [Tag1Key:Tag1Value;Tag2Key:Tag2Value] (行: {excelRow}, 列: {excelCol})"); } return result.ToArray(); } return new FieldTagInfo[0]; } Regex _enumRegex = new Regex(@"enum{name:(\w+)\s*?;\s*?key:(\w+)\s*?;\s*?value:(\w+)\s*?;\s*?summary:(\w+)\s*?;\s*?}"); bool TryGetTableEnum(DataTable dt, Dictionary fieldInfos, string tableName, out TableEnum tableEnum) { // 第5行第一列 var cellVal = dt.Rows[4][0].ToString().ToCellValue(); var match = _enumRegex.Match(cellVal); if (match.Success) { var name = match.Groups[1].Value; var key = match.Groups[2].Value; var value = match.Groups[3].Value; var summary = match.Groups[4].Value; var fieldNames = new HashSet(); foreach(var fieldInfo in fieldInfos.Values) { if (fieldInfo.IsRepeated) continue; fieldNames.Add(fieldInfo.FieldName); } if (!fieldNames.Contains(key)) { throw new Exception($"配置表({tableName}) 枚举类型字段名错误(行:5,列:1): {cellVal}"); } if (!fieldNames.Contains(value)) { throw new Exception($"配置表({tableName}) 枚举值字段名错误(行:5,列:1): {cellVal}"); } if (!fieldNames.Contains(summary)) { throw new Exception($"配置表({tableName}) 枚举说明字段名错误(行:5,列:1): {cellVal}"); } tableEnum = new TableEnum() { name = name, key = key, value = value, summary = summary, }; return true; } tableEnum = null; return false; } List GetRows(DataTable dt, Dictionary fieldInfos, int rowOffset = 4) { int rowCount = dt.Rows.Count; int columnCount = dt.Columns.Count; List rows = new List(); for (int i = rowOffset; i < rowCount; i++) { int emptyCount = 0; List cells = new List(); for (int j = 0; j < columnCount; j++) { if (fieldInfos.TryGetValue(j, out var fieldInfo)) { var value = dt.Rows[i][j].ToString().ToCellValue(); if (string.IsNullOrEmpty(value)) emptyCount++; // last set value cells.Add(new Cell { Row = i, Column = j, Value = value, FieldInfo = fieldInfo }); } } //------------------------------------------- // 过滤掉【全部为空】的情况 (#:filter field) //------------------------------------------- // a b c d #e #f // * * * * * * // * * * * * * // [ ] * * // [ filter ] * * // [ ] * * //------------------------------------------- if (emptyCount < cells.Count) { cells.Sort((a, b) => a.Column.CompareTo(b.Column)); Row row = new Row(); row.Cells = cells; rows.Add(row); } } return rows; } TableTag GetTableTag(DataTable dt, out string newName, string path) { string tableName = string.Empty; //if (dt.Columns.Count > 2) //{ // _ = dt.Rows[0][1].ToString();//表名 // tableName = dt.Rows[0][2].ToString().ToTableName(); //} tableName = string.IsNullOrEmpty(tableName) ? Path.GetFileNameWithoutExtension(path).ToTableName() : tableName; const string C = "c_"; const string S = "s_"; const string E = "e_"; if (tableName.StartsWith(C)) { newName = tableName.Substring(C.Length); return TableTag.Client; } if (tableName.StartsWith(S)) { newName = tableName.Substring(S.Length); return TableTag.Server; } if (tableName.StartsWith(E)) { newName = tableName.Substring(E.Length); return TableTag.Editor; } newName = tableName; return TableTag.All; } } }