package main import ( "bytes" "encoding/json" "flag" "fmt" "github.com/oylshe1314/framework/errors" "github.com/oylshe1314/framework/log" "github.com/oylshe1314/framework/util" "github.com/xuri/excelize/v2" "os" "path" "path/filepath" "reflect" "sort" "strconv" "strings" "time" ) type codeField struct { sort uint32 name string tags string desc string sourceType string golangType string csharpType string formatName string } var goTypeMap = map[string]string{ "bool": "bool", "bool[]": "[]bool", "int": "int", "int[]": "[]int", "long": "int64", "long[]": "[]int64", "float": "float64", "float[]": "[]float64", "double": "float64", "double[]": "[]float64", "string": "string", "string[]": "[]string", "date": "int64", "date[]": "[]int64", "time": "int64", "time[]": "[]int64", "datetime": "int64", "datetime[]": "[]int64", "Vector3": "[3]float64", "Vector3[]": "[][3]float64", } var csTypeMap = map[string]string{ "bool": "bool", "bool[]": "bool[]", "int": "int", "int[]": "int[]", "long": "long", "long[]": "long[]", "float": "float", "float[]": "float[]", "double": "double", "double[]": "double[]", "string": "string", "string[]": "string[]", "date": "long", "date[]": "long[]", "time": "long", "time[]": "long[]", "datetime": "long", "datetime[]": "long[]", "Vector3": "Vector3", "Vector3[]": "Vector3[]", } type exportResult struct { codeFields map[string]*codeField jsonObjects []map[string]interface{} } func printUsage() { var cmdLine = flag.CommandLine var order = []string{"in", "out", "tag", "go", "cs", "clear", "format"} fmt.Printf("Usage of %s:\n", os.Args[0]) for _, s := range order { var f = cmdLine.Lookup(s) if f != nil { name, usage := flag.UnquoteUsage(f) if len(name) > 0 { fmt.Printf(" -%s %s\n", f.Name, name) fmt.Printf(" %s\n\n", usage) } else { fmt.Printf(" -%s %s\n\n", f.Name, usage) } } } } func main() { var clrJson, fmtJson bool var inDir, outDir, jsonTag, goDir, csDir string flag.StringVar(&inDir, "in", "", "Specify the input directory of configuration files") flag.StringVar(&outDir, "out", "", "Specify the output directory for the exported json files") flag.StringVar(&jsonTag, "tag", "", "Specify the tag 'server' or 'client' for the exported json files") flag.StringVar(&goDir, "go", "", "Specify an output directory if it's going to generate go code files") flag.StringVar(&csDir, "cs", "", "Specify an output directory if it's going to generate csharp code files") flag.BoolVar(&clrJson, "clear", false, "Specify if clear the output directory") flag.BoolVar(&fmtJson, "format", false, "Specify if format the output json files") flag.Usage = printUsage flag.Parse() if inDir == "" || outDir == "" || (jsonTag != "" && jsonTag != "server" && jsonTag != "client") { flag.Usage() return } des, err := os.ReadDir(outDir) if err != nil { log.DefaultLogger.Error(err) return } if clrJson { log.DefaultLogger.Info("Clearing the output directory...") for _, de := range des { if de.IsDir() { continue } if !strings.HasSuffix(de.Name(), ".json") { continue } err = os.Remove(path.Join(outDir, de.Name())) if err != nil { log.DefaultLogger.Error(err) return } } } dirs, err := os.ReadDir(inDir) if err != nil { log.DefaultLogger.Error(err) return } log.DefaultLogger.Info("Reading all configuration tables...") var b = time.Now().Unix() var tables []string var tableFiles = make(map[string][]string) for _, dir := range dirs { files, err := readFiles(inDir, dir) if err != nil { log.DefaultLogger.Error(err) return } tables = append(tables, dir.Name()) tableFiles[dir.Name()] = files } //readTables(inDir) var goTables []string var csTables []string for _, table := range tables { files := tableFiles[table] if files == nil { continue } table = util.UpperCamelCase(table) log.DefaultLogger.Infof("Exporting %s...", table) if len(files) == 0 { log.DefaultLogger.Warn("Empty file list") continue } result, err := exportFiles(inDir, files) if err != nil { log.DefaultLogger.Errorf("Export failed, %v", err) continue } if len(result.jsonObjects) == 0 { log.DefaultLogger.Warn("Empty object list") continue } err = writeJsonFile(table, outDir, jsonTag, fmtJson, result) if err != nil { log.DefaultLogger.Errorf("Write %s.json, %v", table, err) continue } if goDir != "" { err = writeGoTable(table, goDir, result) if err == nil { goTables = append(goTables, table) log.DefaultLogger.Infof("Done") } else { log.DefaultLogger.Errorf("Write %s.go failed, %v", table, err) } } if csDir != "" { log.DefaultLogger.Infof("Writing %s.cs...", table) err = writeCsTable(table, csDir, result) if err == nil { csTables = append(csTables, table) log.DefaultLogger.Infof("Done") } else { log.DefaultLogger.Errorf("Write %s.cs failed, %v", table, err) } } } if goDir != "" { log.DefaultLogger.Info("Generating go tables") err = writeGoTables(goTables, goDir) if err == nil { log.DefaultLogger.Infof("Done") } else { log.DefaultLogger.Errorf("Generate tables.go failed, %v", err) } } if csDir != "" { log.DefaultLogger.Info("Generating c-sharp tables") err = writeCsTables(csTables, csDir) if err == nil { log.DefaultLogger.Infof("Done") } else { log.DefaultLogger.Errorf("Generate Tables.cs failed, %v", err) } } var e = time.Now().Unix() log.DefaultLogger.Infof("All configuration tables export finished, times: %ds", e-b) } func filterFields(jsonTag string, doSort bool, result *exportResult) (fields []*codeField) { if jsonTag == "" { fields = util.MapValues(result.codeFields) } else { for _, field := range result.codeFields { if strings.Contains(field.tags, jsonTag) { fields = append(fields, field) } } } if doSort { sort.Slice(fields, func(i, j int) bool { return fields[i].sort < fields[j].sort }) } return } // toStructSlice 这个函数用于把一个map slice(既是JSON Object Array)转为一个动态struct pointer slice // 因为map的key顺序不是固定的,JSON序列化时也无法排序,导致每次输出的JSON文件会有变化,版本控制提交更新时容易冲突 // 但是JSON序列化结构体的字段时是有序的,所以把JSON对象map转成了一个动态的结构体再序列化(!!!绝对是馊主意) // 或者自己写个函数序列化JSON字符串. func toStructSlice(jsonTag string, result *exportResult) (any, error) { var fields = filterFields(jsonTag, true, result) if len(fields) == 0 { return nil, nil } var sfs []reflect.StructField for _, field := range fields { var tipe reflect.Type switch field.golangType { case "bool": tipe = reflect.TypeOf(true) case "[]bool": tipe = reflect.TypeOf([]bool{}) case "int": tipe = reflect.TypeOf(int(0)) case "[]int": tipe = reflect.TypeOf([]int{}) case "int64": tipe = reflect.TypeOf(int64(0)) case "[]int64": tipe = reflect.TypeOf([]int64{}) case "float64": tipe = reflect.TypeOf(float64(0)) case "[]float64": tipe = reflect.TypeOf([]float64{}) case "string": tipe = reflect.TypeOf("") case "[]string": tipe = reflect.TypeOf([]string{}) case "[3]float64": tipe = reflect.TypeOf([3]float64{}) case "[][3]float64": tipe = reflect.TypeOf([][3]float64{}) } sfs = append(sfs, reflect.StructField{Name: field.formatName, Type: tipe, Tag: reflect.StructTag(fmt.Sprintf(`json:"%s,omitempty"`, field.name))}) } var spt = reflect.PointerTo(reflect.StructOf(sfs)) var spsv = reflect.MakeSlice(reflect.SliceOf(spt), len(result.jsonObjects), len(result.jsonObjects)) for si, jsonObject := range result.jsonObjects { var spv, err = util.NewReflectValueFromJson(jsonObject, spt) if err != nil { return nil, err } spsv.Index(si).Set(spv) } return spsv.Interface(), nil } // toJsonString 写的直接序列化JSON字符串的函数 func toJsonString(jsonTag string, result *exportResult) ([]byte, error) { var fields = filterFields(jsonTag, true, result) if len(fields) == 0 { return nil, nil } var buf bytes.Buffer buf.WriteString("[") for oi, jo := range result.jsonObjects { if oi > 0 { buf.WriteString(",") } buf.WriteString("{") for fi, field := range fields { var jv = jo[field.name] if jv != nil { if fi > 0 { buf.WriteString(",") } buf.WriteString("\"") buf.WriteString(field.name) buf.WriteString("\":") vs, err := json.Marshal(jv) if err != nil { return nil, err } buf.WriteString(string(vs)) } } buf.WriteString("}") } buf.WriteString("]\n") return buf.Bytes(), nil } func writeJsonFile(table, outDir, jsonTag string, fmtJson bool, result *exportResult) error { //---------------------- one -------------------------- //var newObjs []map[string]interface{} //if jsonTag == "" { // newObjs = result.jsonObjects //} else { // var fields = filterFields(jsonTag, true, result) // if len(fields) == 0 { // return nil // } // // for _, jsonObj := range result.jsonObjects { // var newObj = map[string]interface{}{} // for _, field := range fields { // var v, ok = jsonObj[field.name] // if ok { // newObj[field.name] = v // } // } // if len(newObj) > 0 { // newObjs = append(newObjs, newObj) // } // } //} // //var jsonFile = filepath.Join(outDir, table+".json") //log.DefaultLogger.Infof("Writing %s...", jsonFile) // //file, err := os.OpenFile(jsonFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) //if err != nil { // return err //} // //defer file.Close() // //var encoder = json.NewEncoder(file) //if fmtJson { // encoder.SetIndent("", " ") //} // //return encoder.Encode(newObjs) //----------------------------------------------------- //---------------------- two -------------------------- //sps, err := toStructSlice(jsonTag, result) //if err != nil { // return err //} // //if sps == nil { // return nil //} // //var jsonFile = filepath.Join(outDir, table+".json") //log.DefaultLogger.Infof("Writing %s...", jsonFile) // //file, err := os.OpenFile(jsonFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) //if err != nil { // return err //} // //defer file.Close() // //var encoder = json.NewEncoder(file) //if fmtJson { // encoder.SetIndent("", " ") //} // //return encoder.Encode(sps) //----------------------------------------------------- //---------------------- thr -------------------------- js, err := toJsonString(jsonTag, result) if err != nil { return err } if js == nil { return nil } if fmtJson { var buf bytes.Buffer err = json.Indent(&buf, js, "", " ") if err != nil { return err } js = buf.Bytes() } var jsonFile = filepath.Join(outDir, table+".json") log.DefaultLogger.Infof("Writing %s...", jsonFile) file, err := os.OpenFile(jsonFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) if err != nil { return err } defer file.Close() _, err = file.Write(js) if err != nil { return err } return nil //----------------------------------------------------- } func writeGoTables(tables []string, goDir string) error { var tablesFile = filepath.Join(goDir, "tables.go") log.DefaultLogger.Infof("Writing %s...", tablesFile) var buf bytes.Buffer var maxTableNameLength = 0 for _, table := range tables { if len(table) > maxTableNameLength { maxTableNameLength = len(table) } } var fmtStr = fmt.Sprintf("\t%%-%ds %%sTable\n", maxTableNameLength) buf.WriteString("package data\n") buf.WriteRune('\n') buf.WriteString("type tables struct {\n") for _, table := range tables { buf.WriteString(fmt.Sprintf(fmtStr, table, table)) } buf.WriteString("}\n") return os.WriteFile(tablesFile, buf.Bytes(), 0666) } func writeGoTable(table, goDir string, result *exportResult) error { var goFile = filepath.Join(goDir, util.LowerSnakeCase(table)+".go") log.DefaultLogger.Infof("Writing %s...", goFile) var buf bytes.Buffer var structName = table var idFieldName = "id" var idField *codeField var maxNameLength = 0 var maxTypeLength = 0 var maxSrcNameLength = 0 var serverFields []*codeField for _, field := range result.codeFields { if !strings.Contains(field.tags, "server") { continue } if len(field.formatName) > maxNameLength { maxNameLength = len(field.formatName) } if len(field.golangType) > maxTypeLength { maxTypeLength = len(field.golangType) } if len(field.name) > maxSrcNameLength { maxSrcNameLength = len(field.name) } if field.name == idFieldName { idField = field switch idField.golangType { case "int": case "int64": case "string": default: return errors.Error("the type of id field just can be int, long, float, double, or string") } } serverFields = append(serverFields, field) } if len(serverFields) == 0 { return errors.Error("the table does not have any fields for server") } if idField == nil { return errors.Error("the table should have an id field") } sort.Slice(serverFields, func(i, j int) bool { return serverFields[i].sort < serverFields[j].sort }) var fmtStr = fmt.Sprintf("\t%%-%ds %%-%ds `json:\"%%s\"`", maxNameLength, maxTypeLength) buf.WriteString("package data\n") buf.WriteRune('\n') buf.WriteString(fmt.Sprintf("type %s struct {\n", structName)) for _, field := range serverFields { buf.WriteString(fmt.Sprintf(fmtStr, field.formatName, field.golangType, field.name)) var spaces = maxSrcNameLength - len(field.name) + 1 for i := 0; i < spaces; i++ { buf.WriteByte(' ') } buf.WriteString(fmt.Sprintf("//%s\n", field.desc)) } buf.WriteString("}\n") buf.WriteRune('\n') buf.WriteString(fmt.Sprintf("func (data *%s) id() %s {\n", structName, idField.golangType)) buf.WriteString(fmt.Sprintf("\treturn data.%s\n", idField.formatName)) buf.WriteString("}\n") buf.WriteRune('\n') buf.WriteString(fmt.Sprintf("type %sTable struct {\n", structName)) buf.WriteString(fmt.Sprintf("\ttable[%s, *%s]\n", idField.golangType, structName)) buf.WriteString("}\n") return os.WriteFile(goFile, buf.Bytes(), 0666) } func writeCsTables(tables []string, csDir string) error { var tablesFile = filepath.Join(csDir, "Tables.cs") log.DefaultLogger.Infof("Writing %s...", tablesFile) var buf bytes.Buffer buf.WriteString("namespace DataTables\n") buf.WriteString("{\n") buf.WriteString("\tpublic static class Tables\n") buf.WriteString("\t{\n") for _, table := range tables { buf.WriteString(fmt.Sprintf("\t\tpublic static %sTable %s\n", table, table)) buf.WriteString("\t\t{\n") buf.WriteString("\t\t\tprivate set { }\n") buf.WriteString("\t\t\tget\n") buf.WriteString("\t\t\t{\n") buf.WriteString(fmt.Sprintf("\t\t\t\tif (%s == null)\n", table)) buf.WriteString("\t\t\t\t{\n") buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new %sTable();\n", table, table)) buf.WriteString("\t\t\t\t}\n") buf.WriteString(fmt.Sprintf("\t\t\t\treturn %s;\n", table)) buf.WriteString("\t\t\t}\n") buf.WriteString("\t\t}\n") buf.WriteString("\n") } buf.WriteString("\t}\n") buf.WriteString("}\n") return os.WriteFile(tablesFile, buf.Bytes(), 0666) } func writeCsTable(table, csDir string, result *exportResult) error { var structName = table var filename = util.UpperCamelCase(table) + ".cs" var idFieldName = "id" var idField *codeField var clientFields []*codeField for _, field := range result.codeFields { if !strings.Contains(field.tags, "client") { continue } if field.name == idFieldName { idField = field switch idField.csharpType { case "int": case "long": case "string": default: return errors.Error("the field type of id should be int, float32, float64, or string") } } clientFields = append(clientFields, field) } if len(clientFields) == 0 { return errors.Error("the table does not have a field for client") } if idField == nil { return errors.Error("the table should have an id field") } sort.Slice(clientFields, func(i, j int) bool { return clientFields[i].sort < clientFields[j].sort }) var buf bytes.Buffer buf.WriteString("using UnityEngine;\n") buf.WriteString("using System.Collections.Generic;\n") buf.WriteRune('\n') buf.WriteString("namespace DataTables\n") buf.WriteString("{\n") buf.WriteString(fmt.Sprintf("\tpublic class %s\n", structName)) buf.WriteString("\t{\n") for i, field := range clientFields { if i > 0 { buf.WriteRune('\n') } buf.WriteString("\t\t/// \n") buf.WriteString(fmt.Sprintf("\t\t/// %s\n", field.desc)) buf.WriteString("\t\t/// \n") buf.WriteString(fmt.Sprintf("\t\tpublic %s %s;\n", field.csharpType, field.formatName)) } buf.WriteString("\t}\n") buf.WriteRune('\n') buf.WriteString(fmt.Sprintf("\tpublic class %sTable : Table<%s, %s>\n", structName, idField.csharpType, structName)) buf.WriteString("\t{\n") //buf.WriteString(fmt.Sprintf("\t\tinternal static %sTable Instance = new %sTable();\n", structName, structName)) buf.WriteString(fmt.Sprintf("\t\tinternal %sTable()\n", structName)) buf.WriteString("\t\t{\n") buf.WriteString(fmt.Sprintf("\t\t\tL = new List<%s>()\n", structName)) buf.WriteString("\t\t\t{\n") for _, jsonObject := range result.jsonObjects { var idValue = jsonObject[idField.name] if idValue == nil { return errors.Error("the table does not have an id value") } buf.WriteString(fmt.Sprintf("\t\t\t\tnew %s()\n", structName)) buf.WriteString("\t\t\t\t{\n") for _, field := range clientFields { var fieldValue = jsonObject[field.name] if isDefaultValue(fieldValue) { continue } switch field.csharpType { case "bool": buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = %v,\n", field.formatName, fieldValue.(bool))) case "bool[]": var ary = fieldValue.([]interface{}) if len(ary) > 0 { buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new bool[]{", field.formatName)) for _, v := range ary { buf.WriteString(fmt.Sprintf("%v, ", v.(bool))) } buf.WriteString("},\n") } case "int": buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = %v,\n", field.formatName, fieldValue.(int))) case "int[]": var ary = fieldValue.([]interface{}) if len(ary) > 0 { buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new int[]{", field.formatName)) for _, v := range ary { buf.WriteString(fmt.Sprintf("%v, ", v.(int))) } buf.WriteString("},\n") } case "float": buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = %vf,\n", field.formatName, fieldValue.(float64))) case "float[]": var ary = fieldValue.([]interface{}) if len(ary) > 0 { buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new float[]{", field.formatName)) for _, v := range ary { buf.WriteString(fmt.Sprintf("%vf, ", v.(float64))) } buf.WriteString("},\n") } case "double": buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = %v,\n", field.formatName, fieldValue.(float64))) case "double[]": var ary = fieldValue.([]interface{}) if len(ary) > 0 { buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new double[]{", field.formatName)) for _, v := range ary { buf.WriteString(fmt.Sprintf("%v, ", v.(float64))) } buf.WriteString("},\n") } case "string": buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = \"%v\",\n", field.formatName, fieldValue.(string))) case "string[]": var ary = fieldValue.([]interface{}) if len(ary) > 0 { buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new string[]{", field.formatName)) for _, v := range ary { buf.WriteString(fmt.Sprintf("\"%v\", ", v.(string))) } buf.WriteString("},\n") } case "Vector3": var vv = fieldValue.([]interface{}) switch len(vv) { case 2: buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new Vector3(%vf, %vf, 0f),\n", field.formatName, vv[0].(float64), vv[1].(float64))) case 3: buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new Vector3(%vf, %vf, %vf),\n", field.formatName, vv[0].(float64), vv[1].(float64), vv[2].(float64))) default: return errors.Errorf("the value of field '%s' invalid for Vector3, id: %v", field.name, idValue) } case "Vector3[]": var ary = fieldValue.([]interface{}) if len(ary) > 0 { buf.WriteString(fmt.Sprintf("\t\t\t\t\t%s = new Vector3[]{", field.formatName)) for _, av := range ary { if av == nil { continue } var vv = av.([]interface{}) if len(vv) > 0 { switch len(vv) { case 2: buf.WriteString(fmt.Sprintf("new Vector3(%vf, %vf, 0f), ", vv[0].(float64), vv[1].(float64))) case 3: buf.WriteString(fmt.Sprintf("new Vector3(%vf, %vf, %vf), ", vv[0].(float64), vv[1].(float64), vv[2].(float64))) default: return errors.Errorf("the value of field '%s' invalid for Vector3, id: %v", field.name, idValue) } } } buf.WriteString("},\n") } } } buf.WriteString("\t\t\t\t},\n") } buf.WriteString("\t\t\t};\n") buf.WriteString(fmt.Sprintf("\t\t\tM = new Dictionary();\n", structName)) buf.WriteString("\t\t\tforeach (var d in L)\n") buf.WriteString("\t\t\t{\n") buf.WriteString(fmt.Sprintf("\t\t\t\tM[d.%s] = d;\n", idField.formatName)) buf.WriteString("\t\t\t}\n") buf.WriteString("\t\t}\n") buf.WriteString("\t}\n") buf.WriteString("}\n") return os.WriteFile(filepath.Join(csDir, filename), buf.Bytes(), 0666) } func readFiles(inDir string, dir os.DirEntry) ([]string, error) { var files []string var dirPath = filepath.Join(inDir, dir.Name()) dirs, err := os.ReadDir(dirPath) if err != nil { return nil, err } for _, sub := range dirs { if sub.IsDir() { subFiles, err := readFiles(dirPath, sub) if err != nil { return nil, err } files = append(files, subFiles...) } else { if path.Ext(sub.Name()) != ".xlsx" { continue } if sub.Name()[0] == '.' || sub.Name()[0] == '~' { continue } files = append(files, filepath.Join(dir.Name(), sub.Name())) } } return files, nil } func exportFiles(inDir string, files []string) (*exportResult, error) { var result exportResult for _, file := range files { var filename = filepath.Join(inDir, file) log.DefaultLogger.Infof("Reading %s...", filename) f, err := excelize.OpenFile(filename) if err != nil { return nil, err } if f.SheetCount == 0 { return nil, errors.Errorf("empty document %s", filename) } for i := 0; i < f.SheetCount; i++ { var sheetName = f.GetSheetName(i) if strings.Contains(sheetName, "help") { continue } err = readSheet(f, sheetName, &result) if err != nil { return nil, err } } _ = f.Close() } return &result, nil } func checkHeadStruct(types, fields, tags []string, codeObjects map[string]*codeField) error { for i, field := range fields { if !strings.Contains(tags[i], "server") && !strings.Contains(tags[i], "client") { continue } ff, ok := codeObjects[strings.TrimSpace(field)] if !ok { return errors.Errorf("inconsistent structures of multiple sheets for the same configuration table, unexpected field '%s'", field) } if strings.TrimSpace(types[i]) != ff.sourceType { return errors.Errorf("inconsistent type of the field '%s' in multiple sheets", field) } if tags[i] != ff.tags { return errors.Errorf("inconsistent tags of the field '%s' in multiple sheets", field) } } return nil } func getColumn(ci int) string { var col, _ = excelize.ColumnNumberToName(ci + 1) return col } func readSheet(excelFile *excelize.File, sheetName string, result *exportResult) error { rows, err := excelFile.GetRows(sheetName) if err != nil { return err } if len(rows) == 0 { return nil } if len(rows) < 4 { return errors.Errorf("sheet '%s' format error, the rows are less 4", sheetName) } var descs = rows[0] var types = rows[1] var fields = rows[2] var tags = rows[3] if len(types) != len(fields) && len(tags) != len(fields) { return errors.Errorf("sheet '%s' format error, inconsistent length of types, fields, and tags", sheetName) } if result.codeFields != nil { err = checkHeadStruct(types, fields, tags, result.codeFields) if err != nil { return errors.Errorf("sheet '%s' format error, %v", sheetName, err) } } rows = rows[4:] for ri, row := range rows { var object = make(map[string]interface{}) for fi, field := range fields { if !strings.Contains(tags[fi], "server") && !strings.Contains(tags[fi], "client") { continue } var fieldName = strings.TrimSpace(field) var fieldType = strings.TrimSpace(types[fi]) if fieldName == "" || fieldType == "" { continue } var srcType = strings.TrimSpace(fieldType) goType, ok := goTypeMap[srcType] if !ok { return errors.Errorf("sheet '%s' row %d column %s can not found go type", sheetName, ri+5, getColumn(fi)) } csType, ok := csTypeMap[srcType] if !ok { return errors.Errorf("sheet '%s' row %d column %s can not found cs type", sheetName, ri+5, getColumn(fi)) } var rowVal = "" if fi < len(row) { rowVal = row[fi] } value, err := readValue(rowVal, srcType) if err != nil { return errors.Errorf("sheet '%s' row %d column %s read value failed, %v", sheetName, ri+5, getColumn(fi), err) } if !isDefaultValue(value) { object[fieldName] = value } if result.codeFields == nil { result.codeFields = map[string]*codeField{} } if result.codeFields[fieldName] == nil { result.codeFields[fieldName] = &codeField{ sort: uint32(fi), name: fieldName, tags: tags[fi], desc: descs[fi], sourceType: fieldType, golangType: goType, csharpType: csType, formatName: util.UpperCamelCase(fieldName), } } } if len(object) > 0 { result.jsonObjects = append(result.jsonObjects, object) } } return nil } func readValue(value, valType string) (interface{}, error) { return parseValue(value, valType) } func isDefaultValue(value interface{}) bool { switch v := value.(type) { case bool: return !v case int, int64, float32, float64: return v == 0 case string: default: return value == nil } return false } func parseValue(value, valType string) (interface{}, error) { switch valType { case "bool": return boolParser(value) case "bool[]": return splitValue(value, ",", boolParser) case "int": return intParser(value) case "int[]": return splitValue(value, ",", intParser) case "float": return doubleParser(value) case "float[]": return splitValue(value, ",", doubleParser) case "double": return doubleParser(value) case "double[]": return splitValue(value, ",", doubleParser) case "string": return stringParser(value) case "string[]": return splitValue(value, ",", stringParser) case "date": return dateParser(value) case "date[]": return splitValue(value, ",", dateParser) case "time": return timeParser(value) case "time[]": return splitValue(value, ",", timeParser) case "datetime": return datetimeParser(value) case "datetime[]": return splitValue(value, ",", datetimeParser) case "Vector3": return vector3Parser(value) case "Vector3[]": return splitValue(value, "|", vector3Parser) default: return stringParser(value) } } func splitValue(value, sep string, parser func(string) (interface{}, error)) (interface{}, error) { if value == "" { return nil, nil } var values []interface{} var items = strings.Split(value, sep) for _, item := range items { parsed, err := parser(item) if err != nil { return nil, err } if parsed != nil { values = append(values, parsed) } } if len(values) == 0 { return nil, nil } return values, nil } func boolParser(item string) (interface{}, error) { item = strings.TrimSpace(item) if item == "" { return false, nil } return strconv.ParseBool(item) } func intParser(item string) (interface{}, error) { item = strings.TrimLeft(strings.TrimSpace(item), "0") if item == "" { return 0, nil } return strconv.Atoi(item) } func doubleParser(item string) (interface{}, error) { item = strings.TrimLeft(strings.TrimSpace(item), "0") if item == "" { return float64(0), nil } if item[0] == '.' { if len(item) == 1 { return float64(0), nil } else { item = "0" + item } } return strconv.ParseFloat(item, 64) } func stringParser(item string) (interface{}, error) { return strings.TrimSpace(item), nil } func dateParser(item string) (interface{}, error) { if item == "" { return int64(0), nil } t, err := time.ParseInLocation(time.DateOnly, item, util.UTC8()) if err != nil { return nil, err } return t.Unix(), nil } func timeParser(item string) (interface{}, error) { if item == "" { return int64(0), nil } t, err := time.ParseInLocation(time.TimeOnly, item, util.UTC8()) if err != nil { return nil, err } return int64(3600*t.Hour() + 60*t.Minute()*t.Second()), nil } func datetimeParser(item string) (interface{}, error) { if item == "" { return int64(0), nil } t, err := time.ParseInLocation(time.DateTime, item, util.UTC8()) if err != nil { return nil, err } return t.Unix(), nil } func vector3Parser(item string) (interface{}, error) { if item == "" { return nil, nil } vectorValue, err := splitValue(item, ",", doubleParser) if err != nil { return nil, err } var ary = vectorValue.([]interface{}) for i := len(ary); i < 3; i++ { ary = append(ary, 0) } return vectorValue, nil }