ecs/tools/exporter/main.go
2025-06-04 18:17:39 +08:00

1155 lines
29 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}