package df import ( "bytes" "encoding/json" "fmt" "go/format" "io/ioutil" "os" "sort" "text/template" "github.com/iancoleman/strcase" "github.com/robertjanetzko/LegendsBrowser2/backend/util" ) type Object struct { Name string `json:"name"` Id bool `json:"id,omitempty"` Named bool `json:"named,omitempty"` Typed bool `json:"typed,omitempty"` SubTypes *[]Subtype `json:"subtypes,omitempty"` SubTypeOf *string `json:"subtypeof,omitempty"` Fields map[string]Field `json:"fields"` } type Subtype struct { Name string `json:"name"` BaseType string `json:"base"` PlusType string `json:"plus"` } type Field struct { Name string `json:"name"` Type string `json:"type"` Multiple bool `json:"multiple,omitempty"` ElementType *string `json:"elements,omitempty"` Legend string `json:"legend"` Base bool Plus bool } func (f Field) TypeLine() string { n := f.Name if n == "Id" || n == "Name" { n = n + "_" } m := "" if f.Multiple { m = "[]" } t := f.Type if f.Type == "array" { t = "[]*" + *f.ElementType } if f.Type == "map" { t = "map[int]*" + *f.ElementType } if f.Type == "object" { t = "*" + *f.ElementType } j := fmt.Sprintf("`json:\"%s\" legend:\"%s\"`", strcase.ToLowerCamel(f.Name), f.Legend) return fmt.Sprintf("%s %s%s %s", n, m, t, j) } func (f Field) Init(plus bool) string { if !plus && f.Type == "map" { return fmt.Sprintf("obj.%s = make(map[int]*%s)", f.Name, *f.ElementType) } return "" } func (f Field) StartAction(plus bool) string { n := f.Name if n == "Id" || n == "Name" { n = n + "_" } if f.Type == "object" { p := fmt.Sprintf("v, _ := parse%s(d, &t)", *f.ElementType) 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" { gen := fmt.Sprintf("parse%s", *f.ElementType) if f.Type == "array" { return fmt.Sprintf("parseArray(d, &obj.%s, %s)", f.Name, gen) } if f.Type == "map" { if !plus { return fmt.Sprintf("parseMap(d, &obj.%s, %s)", f.Name, gen) } else { gen = fmt.Sprintf("parse%sPlus", *f.ElementType) return fmt.Sprintf("parseMapPlus(d, &obj.%s, %s)", 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 "" } func (f Field) Active(plus bool) bool { if plus && f.Plus { return true } if !plus && f.Base { return true } return false } type ActiveSubType struct { Case string Name string Options []string } func (f Object) ActiveSubTypes(plus bool) []*ActiveSubType { subs := make(map[string]*ActiveSubType) for _, s := range *f.SubTypes { if !plus && s.BaseType == "" { continue } if plus && s.PlusType == "" { continue } a := ActiveSubType{} if plus { a.Case = s.PlusType } else { a.Case = s.BaseType } a.Name = f.Name + s.Name a.Options = append(a.Options, a.Name) if sub, ok := subs[a.Case]; ok { sub.Options = append(sub.Options, a.Name) } else { subs[a.Case] = &a } } list := util.Values(subs) sort.SliceStable(list, func(i, j int) bool { return list[i].Case < list[j].Case }) return list } var packageTemplate = template.Must(template.New("").Parse(`// Code generated by legendsbrowser; DO NOT EDIT. package model import ( "encoding/xml" "strconv" "fmt" ) {{- range $name, $obj := $.Objects }} type {{ $obj.Name }} struct { {{- range $fname, $field := $obj.Fields }} {{- if not (and (eq $fname "type") (not (not $obj.SubTypes))) }} {{ $field.TypeLine }} {{- end }} {{- end }} {{- if not (not $obj.SubTypes) }} Details any {{- end }} } {{- 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 }} {{- end }} // Parser func n(d []byte) int { v, _ := strconv.Atoi(string(d)) return v } {{- range $name, $obj := $.Objects }} {{- range $plus := $.Modes }} func parse{{ $obj.Name }}{{ if $plus }}Plus{{ end }}(d *xml.Decoder, start *xml.StartElement{{ if $plus }}, obj *{{ $obj.Name }}{{ end }}) (*{{ $obj.Name }}, error) { var ( {{- if not $plus }} obj = &{{ $obj.Name }}{} {{- end }} data []byte ) {{- if $plus }} if obj == nil { obj = &{{ $obj.Name }}{} } {{- end }} {{- range $fname, $field := $obj.Fields }} {{ $field.Init $plus }} {{- end }} for { tok, err := d.Token() if err != nil { return nil, err } switch t := tok.(type) { case xml.StartElement: switch t.Name.Local { {{- range $fname, $field := $obj.Fields }} {{- if $field.Active $plus }} case "{{ $fname }}": {{ $field.StartAction $plus }} {{- end }} {{- 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 obj, nil } switch t.Name.Local { {{- range $fname, $field := $obj.Fields }}{{- if $field.Active $plus }} case "{{ $fname }}": {{- if and (eq $fname "type") (not (not $obj.SubTypes)) }} var err error switch string(data) { {{- range $sub := ($obj.ActiveSubTypes $plus) }} case "{{ $sub.Case }}": {{- if eq 1 (len $sub.Options) }} {{- if not $plus }} obj.Details, err = parse{{ $sub.Name }}(d, start) {{- else }} obj.Details, err = parse{{ $sub.Name }}Plus(d, start, obj.Details.(*{{ $sub.Name }})) {{- end }} {{- else }} switch details := obj.Details.(type) { {{- range $opt := $sub.Options }} case *{{ $opt}}: obj.Details, err = parse{{ $opt }}Plus(d, start, details) {{- end }} default: fmt.Println("unknown subtype option", obj.Details) d.Skip() } {{- end }} {{- end }} default: d.Skip() } if err != nil { return nil, err } return obj, nil {{- else }} {{ $field.EndAction }} {{- end }} {{- end }}{{- end }} default: // fmt.Println("unknown field", t.Name.Local) } } } } {{- end }} {{- end }} `)) func generateCode(objects *Metadata) error { file, _ := json.MarshalIndent(objects, "", " ") _ = ioutil.WriteFile("model.json", file, 0644) f, err := os.Create("../backend/model/model.go") if err != nil { return err } defer f.Close() var buf bytes.Buffer err = packageTemplate.Execute(&buf, struct { Objects *Metadata Modes []bool }{ Objects: objects, Modes: []bool{false, true}, }) if err != nil { return err } p, err := format.Source(buf.Bytes()) if err != nil { fmt.Println("WARN: could not format source", err) p = buf.Bytes() } _, err = f.Write(p) return err }