parse plus

This commit is contained in:
Robert Janetzko 2022-04-15 13:27:51 +00:00
parent a40e947e60
commit f7b8217d32
6 changed files with 7823 additions and 1582 deletions

View File

@ -2,6 +2,7 @@ package main
import ( import (
"flag" "flag"
"log"
"github.com/robertjanetzko/LegendsBrowser2/analyze/df" "github.com/robertjanetzko/LegendsBrowser2/analyze/df"
) )
@ -16,6 +17,9 @@ func main() {
} }
if *g { if *g {
df.Generate() err := df.Generate()
if err != nil {
log.Fatal(err)
}
} }
} }

View File

@ -115,6 +115,8 @@ func createMetadata(a *AnalyzeData) (*Metadata, error) {
Type: "int", Type: "int",
Multiple: a.Fields[f].Multiple, Multiple: a.Fields[f].Multiple,
Legend: legend, Legend: legend,
Base: a.Fields[f].Base,
Plus: a.Fields[f].Plus,
} }
if ok, elements := isArray(f, fs); ok { if ok, elements := isArray(f, fs); ok {
el := typeNames[elements] el := typeNames[elements]
@ -141,7 +143,7 @@ func createMetadata(a *AnalyzeData) (*Metadata, error) {
Id: a.Fields[k+PATH_SEPARATOR+"id"] != nil, Id: a.Fields[k+PATH_SEPARATOR+"id"] != nil,
Named: a.Fields[k+PATH_SEPARATOR+"name"] != nil, Named: a.Fields[k+PATH_SEPARATOR+"name"] != nil,
Typed: a.Fields[k+PATH_SEPARATOR+"type"] != nil, Typed: a.Fields[k+PATH_SEPARATOR+"type"] != nil,
SubTypes: getSubtypes(objectTypes, k), SubTypes: getSubtypes(a, k),
SubTypeOf: getSubtypeOf(k), SubTypeOf: getSubtypeOf(k),
Fields: objFields, Fields: objFields,
} }
@ -172,20 +174,15 @@ func filterSubtypes(data *map[string]*FieldData) []string {
return list return list
} }
func getSubtypes(objectTypes []string, k string) *[]string { func getSubtypes(a *AnalyzeData, k string) *[]Subtype {
subtypes := make(map[string]bool) if allowedTyped[k] {
if st, ok := a.SubTypes[k]; ok {
for _, t := range objectTypes { var list []Subtype
if strings.HasPrefix(t, k+"+") && !strings.Contains(t[len(k):], PATH_SEPARATOR) { for _, v := range *st {
subtypes[t[strings.LastIndex(t, "+")+1:]] = true list = append(list, *v)
} }
return &list
} }
keys := util.Keys(subtypes)
sort.Strings(keys)
if len(keys) > 0 {
return &keys
} }
return nil return nil

View File

@ -7,9 +7,11 @@ import (
"go/format" "go/format"
"io/ioutil" "io/ioutil"
"os" "os"
"sort"
"text/template" "text/template"
"github.com/iancoleman/strcase" "github.com/iancoleman/strcase"
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
) )
type Object struct { type Object struct {
@ -17,17 +19,25 @@ type Object struct {
Id bool `json:"id,omitempty"` Id bool `json:"id,omitempty"`
Named bool `json:"named,omitempty"` Named bool `json:"named,omitempty"`
Typed bool `json:"typed,omitempty"` Typed bool `json:"typed,omitempty"`
SubTypes *[]string `json:"subtypes,omitempty"` SubTypes *[]Subtype `json:"subtypes,omitempty"`
SubTypeOf *string `json:"subtypeof,omitempty"` SubTypeOf *string `json:"subtypeof,omitempty"`
Fields map[string]Field `json:"fields"` Fields map[string]Field `json:"fields"`
} }
type Subtype struct {
Name string `json:"name"`
BaseType string `json:"base"`
PlusType string `json:"plus"`
}
type Field struct { type Field struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Multiple bool `json:"multiple,omitempty"` Multiple bool `json:"multiple,omitempty"`
ElementType *string `json:"elements,omitempty"` ElementType *string `json:"elements,omitempty"`
Legend string `json:"legend"` Legend string `json:"legend"`
Base bool
Plus bool
} }
func (f Field) TypeLine() string { func (f Field) TypeLine() string {
@ -55,7 +65,15 @@ func (f Field) TypeLine() string {
return fmt.Sprintf("%s %s%s %s", n, m, t, j) return fmt.Sprintf("%s %s%s %s", n, m, t, j)
} }
func (f Field) StartAction() string { 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 n := f.Name
if n == "Id" || n == "Name" { if n == "Id" || n == "Name" {
@ -63,7 +81,7 @@ func (f Field) StartAction() string {
} }
if f.Type == "object" { if f.Type == "object" {
p := fmt.Sprintf("v, _ := parse%s(d, &t)", f.Name) p := fmt.Sprintf("v, _ := parse%s(d, &t)", *f.ElementType)
if !f.Multiple { if !f.Multiple {
return fmt.Sprintf("%s\nobj.%s = v", p, n) return fmt.Sprintf("%s\nobj.%s = v", p, n)
} else { } else {
@ -79,7 +97,12 @@ func (f Field) StartAction() string {
} }
if f.Type == "map" { if f.Type == "map" {
return fmt.Sprintf("obj.%s = make(map[int]*%s)\nparseMap(d, &obj.%s, %s)", f.Name, *f.ElementType, f.Name, gen) 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)
}
} }
} }
@ -114,16 +137,67 @@ func (f Field) EndAction() string {
return "" 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. var packageTemplate = template.Must(template.New("").Parse(`// Code generated by legendsbrowser; DO NOT EDIT.
package model package model
import ( import (
"encoding/xml" "encoding/xml"
"strconv" "strconv"
"github.com/iancoleman/strcase" "fmt"
) )
{{- range $name, $obj := .Objects }} {{- range $name, $obj := $.Objects }}
type {{ $obj.Name }} struct { type {{ $obj.Name }} struct {
{{- range $fname, $field := $obj.Fields }} {{- range $fname, $field := $obj.Fields }}
{{- if not (and (eq $fname "type") (not (not $obj.SubTypes))) }} {{- if not (and (eq $fname "type") (not (not $obj.SubTypes))) }}
@ -150,12 +224,25 @@ func n(d []byte) int {
return v return v
} }
{{- range $name, $obj := .Objects }} {{- range $name, $obj := $.Objects }}
func parse{{ $obj.Name }}(d *xml.Decoder, start *xml.StartElement) (*{{ $obj.Name }}, error) { {{- 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 ( var (
obj = {{ $obj.Name }}{} {{- if not $plus }}
obj = &{{ $obj.Name }}{}
{{- end }}
data []byte data []byte
) )
{{- if $plus }}
if obj == nil {
obj = &{{ $obj.Name }}{}
}
{{- end }}
{{- range $fname, $field := $obj.Fields }}
{{ $field.Init $plus }}
{{- end }}
for { for {
tok, err := d.Token() tok, err := d.Token()
if err != nil { if err != nil {
@ -165,8 +252,10 @@ func parse{{ $obj.Name }}(d *xml.Decoder, start *xml.StartElement) (*{{ $obj.Nam
case xml.StartElement: case xml.StartElement:
switch t.Name.Local { switch t.Name.Local {
{{- range $fname, $field := $obj.Fields }} {{- range $fname, $field := $obj.Fields }}
{{- if $field.Active $plus }}
case "{{ $fname }}": case "{{ $fname }}":
{{ $field.StartAction }} {{ $field.StartAction $plus }}
{{- end }}
{{- end }} {{- end }}
default: default:
// fmt.Println("unknown field", t.Name.Local) // fmt.Println("unknown field", t.Name.Local)
@ -178,18 +267,35 @@ func parse{{ $obj.Name }}(d *xml.Decoder, start *xml.StartElement) (*{{ $obj.Nam
case xml.EndElement: case xml.EndElement:
if t.Name.Local == start.Name.Local { if t.Name.Local == start.Name.Local {
return &obj, nil return obj, nil
} }
switch t.Name.Local { switch t.Name.Local {
{{- range $fname, $field := $obj.Fields }} {{- range $fname, $field := $obj.Fields }}{{- if $field.Active $plus }}
case "{{ $fname }}": case "{{ $fname }}":
{{- if and (eq $fname "type") (not (not $obj.SubTypes)) }} {{- if and (eq $fname "type") (not (not $obj.SubTypes)) }}
var err error var err error
switch strcase.ToCamel(string(data)) { switch string(data) {
{{- range $sub := $obj.SubTypes }} {{- range $sub := ($obj.ActiveSubTypes $plus) }}
case "{{ $sub }}": case "{{ $sub.Case }}":
obj.Details, err = parse{{ $obj.Name }}{{ $sub }}(d, start) {{- 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 }} {{- end }}
default: default:
d.Skip() d.Skip()
@ -197,11 +303,12 @@ func parse{{ $obj.Name }}(d *xml.Decoder, start *xml.StartElement) (*{{ $obj.Nam
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &obj, nil return obj, nil
{{- else }} {{- else }}
{{ $field.EndAction }} {{ $field.EndAction }}
{{- end }} {{- end }}
{{- end }} {{- end }}{{- end }}
default: default:
// fmt.Println("unknown field", t.Name.Local) // fmt.Println("unknown field", t.Name.Local)
} }
@ -209,6 +316,7 @@ func parse{{ $obj.Name }}(d *xml.Decoder, start *xml.StartElement) (*{{ $obj.Nam
} }
} }
{{- end }} {{- end }}
{{- end }}
`)) `))
func generateCode(objects *Metadata) error { func generateCode(objects *Metadata) error {
@ -224,15 +332,18 @@ func generateCode(objects *Metadata) error {
var buf bytes.Buffer var buf bytes.Buffer
err = packageTemplate.Execute(&buf, struct { err = packageTemplate.Execute(&buf, struct {
Objects *Metadata Objects *Metadata
Modes []bool
}{ }{
Objects: objects, Objects: objects,
Modes: []bool{false, true},
}) })
if err != nil { if err != nil {
return err return err
} }
p, err := format.Source(buf.Bytes()) p, err := format.Source(buf.Bytes())
if err != nil { if err != nil {
return err fmt.Println("WARN: could not format source", err)
p = buf.Bytes()
} }
_, err = f.Write(p) _, err = f.Write(p)
return err return err

View File

@ -17,7 +17,7 @@ import (
func AnalyzeStructure(filex string) error { func AnalyzeStructure(filex string) error {
fmt.Println("Search...", filex) fmt.Println("Search...", filex)
files, err := filepath.Glob(filex + "/*.xml") files, err := filepath.Glob(filex + "/*-legends.xml")
if err != nil { if err != nil {
return err return err
} }
@ -45,11 +45,13 @@ func NewFieldData() *FieldData {
type AnalyzeData struct { type AnalyzeData struct {
Fields map[string]*FieldData Fields map[string]*FieldData
SubTypes map[string]*map[string]*Subtype
} }
func NewAnalyzeData() *AnalyzeData { func NewAnalyzeData() *AnalyzeData {
return &AnalyzeData{ return &AnalyzeData{
Fields: make(map[string]*FieldData, 0), Fields: make(map[string]*FieldData, 0),
SubTypes: make(map[string]*map[string]*Subtype),
} }
} }
@ -82,29 +84,86 @@ func (a *AnalyzeData) GetField(s string) *FieldData {
} }
} }
func analyze(file string, a *AnalyzeData) error { func (a *AnalyzeData) GetSubType(s string, t string) *Subtype {
xmlFile, err := os.Open(file) var (
if err != nil { st *map[string]*Subtype
fmt.Println(err) su *Subtype
ok bool
)
if st, ok = a.SubTypes[s]; !ok {
x := make(map[string]*Subtype)
a.SubTypes[s] = &x
st = &x
} }
plus := strings.HasSuffix(file, "_plus.xml") if su, ok = (*st)[t]; !ok {
x := Subtype{Name: t}
(*st)[t] = &x
su = &x
}
return su
}
type AnalyzeContext struct {
file string
plus bool
subtypes map[string]map[int]string
}
func analyze(file string, a *AnalyzeData) error {
ctx := AnalyzeContext{
file: file,
plus: false,
subtypes: make(map[string]map[int]string),
}
// base file
xmlFile, err := os.Open(file)
if err != nil {
return err
}
fmt.Println("Successfully Opened", file) fmt.Println("Successfully Opened", file)
defer xmlFile.Close() defer xmlFile.Close()
converter := util.NewConvertReader(xmlFile) _, err = analyzeElement(xml.NewDecoder(util.NewConvertReader(xmlFile)), a, make([]string, 0), &ctx)
if err != nil {
return err
}
return analyzeElement(xml.NewDecoder(converter), a, make([]string, 0), plus) // plus file
ctx.plus = true
file = strings.Replace(file, "-legends.xml", "-legends_plus.xml", 1)
xmlFile, err = os.Open(file)
if err != nil {
return err
}
fmt.Println("Successfully Opened", file)
defer xmlFile.Close()
_, err = analyzeElement(xml.NewDecoder(util.NewConvertReader(xmlFile)), a, make([]string, 0), &ctx)
return err
} }
const PATH_SEPARATOR = "|" const PATH_SEPARATOR = "|"
func analyzeElement(d *xml.Decoder, a *AnalyzeData, path []string, plus bool) error { type Value struct {
Name string
Value string
}
func analyzeElement(d *xml.Decoder, a *AnalyzeData, path []string, ctx *AnalyzeContext) (*Value, error) {
if len(path) > 1 { if len(path) > 1 {
s := strings.Join(path, PATH_SEPARATOR) s := strings.Join(path, PATH_SEPARATOR)
fd := a.GetField(s) fd := a.GetField(s)
if plus { if ctx.plus {
fd.Plus = true fd.Plus = true
} else { } else {
fd.Base = true fd.Base = true
@ -113,6 +172,10 @@ func analyzeElement(d *xml.Decoder, a *AnalyzeData, path []string, plus bool) er
var ( var (
data []byte data []byte
id int
idFound bool
subtype string
subtypeFound bool
) )
value := true value := true
@ -124,7 +187,7 @@ Loop:
if err == io.EOF { if err == io.EOF {
break Loop break Loop
} else if err != nil { } else if err != nil {
return err return nil, err
} }
switch t := tok.(type) { switch t := tok.(type) {
case xml.StartElement: case xml.StartElement:
@ -137,24 +200,64 @@ Loop:
} }
fields[t.Name.Local] = true fields[t.Name.Local] = true
analyzeElement(d, a, newPath, plus) v, err := analyzeElement(d, a, newPath, ctx)
if err != nil {
return nil, err
}
if v != nil {
if v.Name == "id" {
idFound = true
id, _ = strconv.Atoi(v.Value)
}
if v.Name == "type" {
subtypeFound = true
subtype = v.Value
if idFound && subtypeFound {
p := strings.Join(path, PATH_SEPARATOR)
if strings.Contains(p, "+") {
p = p[:strings.LastIndex(p, "+")]
}
typeMap, ok := ctx.subtypes[p]
if !ok {
typeMap = make(map[int]string)
ctx.subtypes[p] = typeMap
}
if !ctx.plus {
typeMap[id] = subtype
a.GetSubType(p, strcase.ToCamel(subtype)).BaseType = subtype
} else {
if typeMap[id] != subtype {
if typeMap[id] != "" {
a.GetSubType(p, strcase.ToCamel(typeMap[id])).PlusType = subtype
} else {
a.GetSubType(p, strcase.ToCamel(subtype)).PlusType = subtype
}
subtype = typeMap[id]
} else {
a.GetSubType(p, strcase.ToCamel(subtype)).PlusType = subtype
}
}
}
path[len(path)-1] = path[len(path)-1] + "+" + strcase.ToCamel(subtype)
}
}
case xml.CharData: case xml.CharData:
data = append(data, t...) data = append(data, t...)
case xml.EndElement: case xml.EndElement:
if value { if value {
if _, err := strconv.Atoi(string(data)); err != nil { s := string(data)
if _, err := strconv.Atoi(s); err != nil {
a.GetField(strings.Join(path, PATH_SEPARATOR)).IsString = true a.GetField(strings.Join(path, PATH_SEPARATOR)).IsString = true
} }
return &Value{Name: t.Name.Local, Value: s}, nil
} }
if t.Name.Local == "type" { return nil, err
path[len(path)-2] = path[len(path)-2] + "+" + strcase.ToCamel(string(data))
}
return nil
} }
} }
return nil return nil, nil
} }

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,10 @@ package model
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"log"
"os" "os"
"strconv"
"strings"
"github.com/robertjanetzko/LegendsBrowser2/backend/util" "github.com/robertjanetzko/LegendsBrowser2/backend/util"
) )
@ -23,6 +26,8 @@ func Parse(file string) (*DfWorld, error) {
converter := util.NewConvertReader(xmlFile) converter := util.NewConvertReader(xmlFile)
d := xml.NewDecoder(converter) d := xml.NewDecoder(converter)
var world *DfWorld
BaseLoop:
for { for {
tok, err := d.Token() tok, err := d.Token()
if err != nil { if err != nil {
@ -31,10 +36,51 @@ func Parse(file string) (*DfWorld, error) {
switch t := tok.(type) { switch t := tok.(type) {
case xml.StartElement: case xml.StartElement:
if t.Name.Local == "df_world" { if t.Name.Local == "df_world" {
return parseDfWorld(d, &t) world, err = parseDfWorld(d, &t)
if err != nil {
return nil, err
}
break BaseLoop
} }
} }
} }
plus := true
if plus {
file = strings.Replace(file, "-legends.xml", "-legends_plus.xml", 1)
xmlFile, err := os.Open(file)
if err != nil {
fmt.Println(err)
return world, nil
}
fmt.Println("Successfully Opened", file)
defer xmlFile.Close()
converter := util.NewConvertReader(xmlFile)
d := xml.NewDecoder(converter)
PlusLoop:
for {
tok, err := d.Token()
if err != nil {
return nil, err
}
switch t := tok.(type) {
case xml.StartElement:
if t.Name.Local == "df_world" {
world, err = parseDfWorldPlus(d, &t, world)
if err != nil {
return nil, err
}
break PlusLoop
}
}
}
}
return world, nil
} }
func parseArray[T any](d *xml.Decoder, dest *[]T, creator func(*xml.Decoder, *xml.StartElement) (T, error)) { func parseArray[T any](d *xml.Decoder, dest *[]T, creator func(*xml.Decoder, *xml.StartElement) (T, error)) {
@ -70,3 +116,52 @@ func parseMap[T Identifiable](d *xml.Decoder, dest *map[int]T, creator func(*xml
} }
} }
} }
func parseMapPlus[T Identifiable](d *xml.Decoder, dest *map[int]T, creator func(*xml.Decoder, *xml.StartElement, T) (T, error)) {
for {
tok, err := d.Token()
if err != nil {
return
}
switch t := tok.(type) {
case xml.StartElement:
id, err := parseId(d)
if err != nil {
log.Fatal(err)
}
x, err := creator(d, &t, (*dest)[id])
if err != nil {
return
}
(*dest)[id] = x
case xml.EndElement:
return
}
}
}
func parseId(d *xml.Decoder) (int, error) {
var data []byte
for {
tok, err := d.Token()
if err != nil {
return -1, err
}
switch t := tok.(type) {
case xml.StartElement:
data = nil
if t.Name.Local != "id" {
d.Skip()
// return -1, fmt.Errorf("expected id at: %d", d.InputOffset())
}
case xml.CharData:
data = append(data, t...)
case xml.EndElement:
if t.Name.Local == "id" {
return strconv.Atoi(string(data))
}
}
}
}