ecs/tools/exporter/main.go

1155 lines
29 KiB
Go
Raw Normal View History

2025-06-04 18:17:39 +08:00
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/// <summary>\n")
buf.WriteString(fmt.Sprintf("\t\t/// %s\n", field.desc))
buf.WriteString("\t\t/// </summary>\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<int, %s>();\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
}