2022-04-13 08:28:07 +03:00
|
|
|
package analyze
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"encoding/xml"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"legendsbrowser/util"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"text/template"
|
|
|
|
|
|
|
|
"github.com/iancoleman/strcase"
|
|
|
|
)
|
|
|
|
|
|
|
|
func Analyze(filex string) {
|
2022-04-13 15:01:20 +03:00
|
|
|
fmt.Println("Search...", filex)
|
|
|
|
files, err := filepath.Glob(filex + "/*.xml")
|
2022-04-13 08:28:07 +03:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
fmt.Println(files)
|
|
|
|
|
|
|
|
a := NewAnalyzeData()
|
|
|
|
|
|
|
|
for _, file := range files {
|
2022-04-13 15:01:20 +03:00
|
|
|
analyze(file, a)
|
|
|
|
}
|
2022-04-13 08:28:07 +03:00
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
file, _ := json.MarshalIndent(a, "", " ")
|
|
|
|
_ = ioutil.WriteFile("analyze.json", file, 0644)
|
|
|
|
|
|
|
|
createMetadata(a)
|
|
|
|
}
|
2022-04-13 08:28:07 +03:00
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
func Generate() {
|
|
|
|
data, err := ioutil.ReadFile("analyze.json")
|
|
|
|
if err != nil {
|
|
|
|
return
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
a := NewAnalyzeData()
|
|
|
|
json.Unmarshal(data, a)
|
2022-04-13 08:28:07 +03:00
|
|
|
createMetadata(a)
|
|
|
|
}
|
|
|
|
|
2022-04-14 09:07:44 +03:00
|
|
|
type FieldData struct {
|
|
|
|
IsString bool
|
|
|
|
Multiple bool
|
|
|
|
Base bool
|
|
|
|
Plus bool
|
|
|
|
}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
type AnalyzeData struct {
|
2022-04-14 09:07:44 +03:00
|
|
|
// Types map[string]bool
|
|
|
|
Fields map[string]*FieldData
|
2022-04-13 15:01:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewAnalyzeData() *AnalyzeData {
|
|
|
|
return &AnalyzeData{
|
2022-04-14 09:07:44 +03:00
|
|
|
// Types: make(map[string]bool, 0),
|
|
|
|
Fields: make(map[string]*FieldData, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *AnalyzeData) GetField(s string) *FieldData {
|
|
|
|
if f, ok := a.Fields[s]; ok {
|
|
|
|
return f
|
|
|
|
} else {
|
|
|
|
f := &FieldData{}
|
|
|
|
a.Fields[s] = f
|
|
|
|
return f
|
2022-04-13 15:01:20 +03:00
|
|
|
}
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
func analyze(file string, a *AnalyzeData) error {
|
|
|
|
xmlFile, err := os.Open(file)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
2022-04-13 15:01:20 +03:00
|
|
|
|
|
|
|
plus := strings.HasSuffix(file, "_plus.xml")
|
|
|
|
|
|
|
|
fmt.Println("Successfully Opened", file)
|
|
|
|
defer xmlFile.Close()
|
|
|
|
|
|
|
|
converter := util.NewConvertReader(xmlFile)
|
|
|
|
|
|
|
|
return analyzeElement(xml.NewDecoder(converter), a, make([]string, 0), plus)
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
const PATH_SEPARATOR = "|"
|
|
|
|
|
|
|
|
func analyzeElement(d *xml.Decoder, a *AnalyzeData, path []string, plus bool) error {
|
|
|
|
if len(path) > 1 {
|
|
|
|
s := strings.Join(path, PATH_SEPARATOR)
|
|
|
|
a.Fields[s] = true
|
|
|
|
if plus {
|
|
|
|
a.Plus[s] = true
|
|
|
|
} else {
|
|
|
|
a.Base[s] = true
|
|
|
|
}
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
data []byte
|
|
|
|
)
|
|
|
|
value := true
|
|
|
|
|
|
|
|
fields := make(map[string]bool)
|
|
|
|
|
|
|
|
Loop:
|
|
|
|
for {
|
|
|
|
tok, err := d.Token()
|
|
|
|
if err == io.EOF {
|
|
|
|
break Loop
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch t := tok.(type) {
|
|
|
|
case xml.StartElement:
|
|
|
|
value = false
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
a.Types[strings.Join(path, PATH_SEPARATOR)] = true
|
2022-04-13 08:28:07 +03:00
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
newPath := append(path, t.Name.Local)
|
2022-04-13 08:28:07 +03:00
|
|
|
|
|
|
|
if _, ok := fields[t.Name.Local]; ok {
|
2022-04-13 15:01:20 +03:00
|
|
|
a.Multiple[strings.Join(newPath, PATH_SEPARATOR)] = true
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
fields[t.Name.Local] = true
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
analyzeElement(d, a, newPath, plus)
|
2022-04-13 08:28:07 +03:00
|
|
|
|
|
|
|
case xml.CharData:
|
|
|
|
data = append(data, t...)
|
|
|
|
|
|
|
|
case xml.EndElement:
|
|
|
|
if value {
|
|
|
|
if _, err := strconv.Atoi(string(data)); err != nil {
|
2022-04-13 15:01:20 +03:00
|
|
|
a.IsString[strings.Join(path, PATH_SEPARATOR)] = true
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 09:07:44 +03:00
|
|
|
if t.Name.Local == "type" {
|
|
|
|
path[len(path)-2] = path[len(path)-2] + "+" + strcase.ToCamel(string(data))
|
|
|
|
}
|
2022-04-13 08:28:07 +03:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
func filterSubtypes(data map[string]bool) []string {
|
|
|
|
allowed := map[string]bool{
|
|
|
|
"df_world|historical_events|historical_event": true,
|
|
|
|
"df_world|historical_event_collections|historical_event_collection": true,
|
|
|
|
}
|
|
|
|
|
|
|
|
filtered := make(map[string]bool)
|
|
|
|
for k, v := range data {
|
|
|
|
if !v {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
path := strings.Split(k, PATH_SEPARATOR)
|
|
|
|
for index, seg := range path {
|
|
|
|
if strings.Contains(seg, "+") {
|
|
|
|
base := seg[:strings.Index(seg, "+")]
|
|
|
|
basePath := strings.Join(append(path[:index], base), PATH_SEPARATOR)
|
|
|
|
if allowed[basePath] {
|
|
|
|
path[index] = seg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
filtered[strings.Join(path, PATH_SEPARATOR)] = true
|
|
|
|
}
|
|
|
|
list := util.Keys(filtered)
|
|
|
|
sort.Strings(list)
|
|
|
|
return list
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
func createMetadata(a *AnalyzeData) {
|
|
|
|
ts := filterSubtypes(a.Types)
|
|
|
|
fs := filterSubtypes(a.Fields)
|
2022-04-13 08:28:07 +03:00
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
// for _, s := range fs {
|
|
|
|
// fmt.Println(s)
|
|
|
|
// }
|
2022-04-13 08:28:07 +03:00
|
|
|
|
|
|
|
objects := make(map[string]Object, 0)
|
|
|
|
|
|
|
|
for _, k := range ts {
|
|
|
|
if ok, _ := isArray(k, fs); !ok {
|
|
|
|
n := k
|
2022-04-13 15:01:20 +03:00
|
|
|
if strings.Contains(k, PATH_SEPARATOR) {
|
|
|
|
n = k[strings.LastIndex(k, PATH_SEPARATOR)+1:]
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if n == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
objFields := make(map[string]Field, 0)
|
|
|
|
|
|
|
|
for _, f := range fs {
|
2022-04-13 15:01:20 +03:00
|
|
|
if strings.HasPrefix(f, k+PATH_SEPARATOR) {
|
2022-04-13 08:28:07 +03:00
|
|
|
fn := f[len(k)+1:]
|
2022-04-13 15:01:20 +03:00
|
|
|
if !strings.Contains(fn, PATH_SEPARATOR) {
|
|
|
|
legend := ""
|
|
|
|
if a.Base[f] && a.Plus[f] {
|
|
|
|
legend = "both"
|
|
|
|
} else if a.Base[f] {
|
|
|
|
legend = "base"
|
|
|
|
} else if a.Plus[f] {
|
|
|
|
legend = "plus"
|
|
|
|
}
|
2022-04-13 08:28:07 +03:00
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
field := Field{
|
|
|
|
Name: strcase.ToCamel(fn),
|
|
|
|
Type: "int",
|
|
|
|
Multiple: a.Multiple[f],
|
|
|
|
Legend: legend,
|
|
|
|
}
|
2022-04-13 08:28:07 +03:00
|
|
|
if ok, elements := isArray(f, fs); ok {
|
2022-04-13 15:01:20 +03:00
|
|
|
el := elements[strings.LastIndex(elements, PATH_SEPARATOR)+1:]
|
|
|
|
fmt.Println(f + PATH_SEPARATOR + elements + PATH_SEPARATOR + "id")
|
|
|
|
if a.Fields[elements+PATH_SEPARATOR+"id"] {
|
|
|
|
field.Type = "map"
|
|
|
|
} else {
|
|
|
|
field.Type = "array"
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
2022-04-13 15:01:20 +03:00
|
|
|
field.ElementType = &(el)
|
2022-04-13 08:28:07 +03:00
|
|
|
} else if ok, _ := isObject(f, fs); ok {
|
2022-04-13 15:01:20 +03:00
|
|
|
field.Type = "object"
|
|
|
|
} else if a.IsString[f] {
|
|
|
|
field.Type = "string"
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
2022-04-13 15:01:20 +03:00
|
|
|
objFields[fn] = field
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
objects[n] = Object{
|
|
|
|
Name: strcase.ToCamel(n),
|
2022-04-13 15:01:20 +03:00
|
|
|
Id: a.Fields[k+PATH_SEPARATOR+"id"],
|
|
|
|
Named: a.Fields[k+PATH_SEPARATOR+"name"],
|
|
|
|
Typed: a.Fields[k+PATH_SEPARATOR+"type"],
|
2022-04-13 08:28:07 +03:00
|
|
|
Fields: objFields,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
file, _ := json.MarshalIndent(objects, "", " ")
|
|
|
|
_ = ioutil.WriteFile("model.json", file, 0644)
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
f, err := os.Create("df/model.go")
|
2022-04-13 08:28:07 +03:00
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
err = packageTemplate.Execute(f, struct {
|
|
|
|
Objects map[string]Object
|
|
|
|
}{
|
|
|
|
Objects: objects,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isArray(typ string, types []string) (bool, string) {
|
|
|
|
fc := 0
|
|
|
|
elements := ""
|
|
|
|
|
|
|
|
for _, t := range types {
|
2022-04-13 15:01:20 +03:00
|
|
|
if !strings.HasPrefix(t, typ+PATH_SEPARATOR) {
|
2022-04-13 08:28:07 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
f := t[len(typ)+1:]
|
2022-04-13 15:01:20 +03:00
|
|
|
if strings.Contains(f, PATH_SEPARATOR) {
|
2022-04-13 08:28:07 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
fc++
|
|
|
|
elements = t
|
|
|
|
}
|
|
|
|
return fc == 1, elements
|
|
|
|
}
|
|
|
|
|
|
|
|
func isObject(typ string, types []string) (bool, string) {
|
|
|
|
fc := 0
|
|
|
|
|
|
|
|
for _, t := range types {
|
2022-04-13 15:01:20 +03:00
|
|
|
if !strings.HasPrefix(t, typ+PATH_SEPARATOR) {
|
2022-04-13 08:28:07 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
fc++
|
|
|
|
}
|
|
|
|
return fc > 0, typ
|
|
|
|
}
|
|
|
|
|
|
|
|
type Object struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Id bool `json:"id,omitempty"`
|
|
|
|
Named bool `json:"named,omitempty"`
|
|
|
|
Typed bool `json:"typed,omitempty"`
|
|
|
|
Fields map[string]Field `json:"fields"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Field struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Multiple bool `json:"multiple,omitempty"`
|
|
|
|
ElementType *string `json:"elements,omitempty"`
|
2022-04-13 15:01:20 +03:00
|
|
|
Legend string `json:"legend"`
|
2022-04-13 08:28:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f Field) TypeLine(objects map[string]Object) string {
|
|
|
|
n := f.Name
|
|
|
|
|
|
|
|
if n == "Id" || n == "Name" {
|
|
|
|
n = n + "_"
|
|
|
|
}
|
|
|
|
|
|
|
|
m := ""
|
|
|
|
if f.Multiple {
|
|
|
|
m = "[]"
|
|
|
|
}
|
|
|
|
t := f.Type
|
|
|
|
if f.Type == "array" {
|
2022-04-13 15:01:20 +03:00
|
|
|
t = "[]*" + objects[*f.ElementType].Name
|
|
|
|
}
|
|
|
|
if f.Type == "map" {
|
2022-04-13 08:28:07 +03:00
|
|
|
t = "map[int]*" + objects[*f.ElementType].Name
|
|
|
|
}
|
|
|
|
if f.Type == "object" {
|
|
|
|
t = f.Name
|
|
|
|
}
|
2022-04-13 15:01:20 +03:00
|
|
|
j := fmt.Sprintf("`json:\"%s\" legend:\"%s\"`", strcase.ToLowerCamel(f.Name), f.Legend)
|
2022-04-13 08:28:07 +03:00
|
|
|
return fmt.Sprintf("%s %s%s %s", n, m, t, j)
|
|
|
|
}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
func (f Field) StartAction() string {
|
|
|
|
n := f.Name
|
|
|
|
|
|
|
|
if n == "Id" || n == "Name" {
|
|
|
|
n = n + "_"
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Type == "object" {
|
|
|
|
p := fmt.Sprintf("v := %s{}\nv.Parse(d, &t)", f.Name)
|
|
|
|
if !f.Multiple {
|
|
|
|
return fmt.Sprintf("%s\nobj.%s = v", p, n)
|
|
|
|
} else {
|
|
|
|
return fmt.Sprintf("%s\nobj.%s = append(obj.%s, v)", p, n, n)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Type == "array" || f.Type == "map" {
|
|
|
|
el := strcase.ToCamel(*f.ElementType)
|
|
|
|
gen := fmt.Sprintf("New%s", el)
|
|
|
|
|
|
|
|
if f.Type == "array" {
|
|
|
|
return fmt.Sprintf("parseArray(d, &obj.%s, %s)", f.Name, gen)
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Type == "map" {
|
|
|
|
return fmt.Sprintf("obj.%s = make(map[int]*%s)\nparseMap(d, &obj.%s, %s)", f.Name, el, f.Name, gen)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Type == "int" || f.Type == "string" {
|
|
|
|
return "data = nil"
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f Field) EndAction() string {
|
|
|
|
n := f.Name
|
|
|
|
|
|
|
|
if n == "Id" || n == "Name" {
|
|
|
|
n = n + "_"
|
|
|
|
}
|
|
|
|
|
|
|
|
if !f.Multiple {
|
|
|
|
if f.Type == "int" {
|
|
|
|
return fmt.Sprintf("obj.%s = n(data)", n)
|
|
|
|
} else if f.Type == "string" {
|
|
|
|
return fmt.Sprintf("obj.%s = string(data)", n)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if f.Type == "int" {
|
|
|
|
return fmt.Sprintf("obj.%s = append(obj.%s, n(data))", n, n)
|
|
|
|
} else if f.Type == "string" {
|
|
|
|
return fmt.Sprintf("obj.%s = append(obj.%s, string(data))", n, n)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2022-04-13 08:28:07 +03:00
|
|
|
var packageTemplate = template.Must(template.New("").Parse(`// Code generated by go generate; DO NOT EDIT.
|
2022-04-13 15:01:20 +03:00
|
|
|
package df
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/xml"
|
|
|
|
"strconv"
|
|
|
|
)
|
2022-04-13 08:28:07 +03:00
|
|
|
|
|
|
|
{{- range $name, $obj := .Objects }}
|
|
|
|
type {{ $obj.Name }} struct {
|
|
|
|
{{- range $fname, $field := $obj.Fields }}
|
|
|
|
{{ $field.TypeLine $.Objects }}
|
|
|
|
{{- end }}
|
|
|
|
}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
func New{{ $obj.Name }}() *{{ $obj.Name }} { return &{{ $obj.Name }}{} }
|
2022-04-13 08:28:07 +03:00
|
|
|
{{- if $obj.Id }}
|
|
|
|
func (x *{{ $obj.Name }}) Id() int { return x.Id_ }
|
|
|
|
{{- end }}
|
|
|
|
{{- if $obj.Named }}
|
|
|
|
func (x *{{ $obj.Name }}) Name() string { return x.Name_ }
|
|
|
|
{{- end }}
|
|
|
|
|
2022-04-13 15:01:20 +03:00
|
|
|
|
|
|
|
|
|
|
|
{{- end }}
|
|
|
|
|
|
|
|
// Parser
|
|
|
|
|
|
|
|
func n(d []byte) int {
|
|
|
|
v, _ := strconv.Atoi(string(d))
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
{{- range $name, $obj := .Objects }}
|
|
|
|
func (obj *{{ $obj.Name }}) Parse(d *xml.Decoder, start *xml.StartElement) error {
|
|
|
|
var data []byte
|
|
|
|
|
|
|
|
for {
|
|
|
|
tok, err := d.Token()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch t := tok.(type) {
|
|
|
|
case xml.StartElement:
|
|
|
|
switch t.Name.Local {
|
|
|
|
{{- range $fname, $field := $obj.Fields }}
|
|
|
|
case "{{ $fname }}":
|
|
|
|
{{ $field.StartAction }}
|
|
|
|
{{- end }}
|
|
|
|
default:
|
|
|
|
// fmt.Println("unknown field", t.Name.Local)
|
|
|
|
d.Skip()
|
|
|
|
}
|
|
|
|
|
|
|
|
case xml.CharData:
|
|
|
|
data = append(data, t...)
|
|
|
|
|
|
|
|
case xml.EndElement:
|
|
|
|
if t.Name.Local == start.Name.Local {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch t.Name.Local {
|
|
|
|
{{- range $fname, $field := $obj.Fields }}
|
|
|
|
case "{{ $fname }}":
|
|
|
|
{{ $field.EndAction }}
|
|
|
|
{{- end }}
|
|
|
|
default:
|
|
|
|
// fmt.Println("unknown field", t.Name.Local)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-13 08:28:07 +03:00
|
|
|
{{- end }}
|
|
|
|
`))
|