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 (
"flag"
"log"
"github.com/robertjanetzko/LegendsBrowser2/analyze/df"
)
@ -16,6 +17,9 @@ func main() {
}
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",
Multiple: a.Fields[f].Multiple,
Legend: legend,
Base: a.Fields[f].Base,
Plus: a.Fields[f].Plus,
}
if ok, elements := isArray(f, fs); ok {
el := typeNames[elements]
@ -141,7 +143,7 @@ func createMetadata(a *AnalyzeData) (*Metadata, error) {
Id: a.Fields[k+PATH_SEPARATOR+"id"] != nil,
Named: a.Fields[k+PATH_SEPARATOR+"name"] != nil,
Typed: a.Fields[k+PATH_SEPARATOR+"type"] != nil,
SubTypes: getSubtypes(objectTypes, k),
SubTypes: getSubtypes(a, k),
SubTypeOf: getSubtypeOf(k),
Fields: objFields,
}
@ -172,22 +174,17 @@ func filterSubtypes(data *map[string]*FieldData) []string {
return list
}
func getSubtypes(objectTypes []string, k string) *[]string {
subtypes := make(map[string]bool)
for _, t := range objectTypes {
if strings.HasPrefix(t, k+"+") && !strings.Contains(t[len(k):], PATH_SEPARATOR) {
subtypes[t[strings.LastIndex(t, "+")+1:]] = true
func getSubtypes(a *AnalyzeData, k string) *[]Subtype {
if allowedTyped[k] {
if st, ok := a.SubTypes[k]; ok {
var list []Subtype
for _, v := range *st {
list = append(list, *v)
}
return &list
}
}
keys := util.Keys(subtypes)
sort.Strings(keys)
if len(keys) > 0 {
return &keys
}
return nil
}

View file

@ -7,9 +7,11 @@ import (
"go/format"
"io/ioutil"
"os"
"sort"
"text/template"
"github.com/iancoleman/strcase"
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
)
type Object struct {
@ -17,17 +19,25 @@ type Object struct {
Id bool `json:"id,omitempty"`
Named bool `json:"named,omitempty"`
Typed bool `json:"typed,omitempty"`
SubTypes *[]string `json:"subtypes,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 {
@ -55,7 +65,15 @@ func (f Field) TypeLine() string {
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
if n == "Id" || n == "Name" {
@ -63,7 +81,7 @@ func (f Field) StartAction() string {
}
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 {
return fmt.Sprintf("%s\nobj.%s = v", p, n)
} else {
@ -79,7 +97,12 @@ func (f Field) StartAction() string {
}
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 ""
}
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"
"github.com/iancoleman/strcase"
"fmt"
)
{{- range $name, $obj := .Objects }}
{{- range $name, $obj := $.Objects }}
type {{ $obj.Name }} struct {
{{- range $fname, $field := $obj.Fields }}
{{- if not (and (eq $fname "type") (not (not $obj.SubTypes))) }}
@ -150,12 +224,25 @@ func n(d []byte) int {
return v
}
{{- range $name, $obj := .Objects }}
func parse{{ $obj.Name }}(d *xml.Decoder, start *xml.StartElement) (*{{ $obj.Name }}, error) {
{{- 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 (
obj = {{ $obj.Name }}{}
{{- 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 {
@ -165,8 +252,10 @@ func parse{{ $obj.Name }}(d *xml.Decoder, start *xml.StartElement) (*{{ $obj.Nam
case xml.StartElement:
switch t.Name.Local {
{{- range $fname, $field := $obj.Fields }}
{{- if $field.Active $plus }}
case "{{ $fname }}":
{{ $field.StartAction }}
{{ $field.StartAction $plus }}
{{- end }}
{{- end }}
default:
// 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:
if t.Name.Local == start.Name.Local {
return &obj, nil
return obj, nil
}
switch t.Name.Local {
{{- range $fname, $field := $obj.Fields }}
{{- range $fname, $field := $obj.Fields }}{{- if $field.Active $plus }}
case "{{ $fname }}":
{{- if and (eq $fname "type") (not (not $obj.SubTypes)) }}
var err error
switch strcase.ToCamel(string(data)) {
{{- range $sub := $obj.SubTypes }}
case "{{ $sub }}":
obj.Details, err = parse{{ $obj.Name }}{{ $sub }}(d, start)
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()
@ -197,11 +303,12 @@ func parse{{ $obj.Name }}(d *xml.Decoder, start *xml.StartElement) (*{{ $obj.Nam
if err != nil {
return nil, err
}
return &obj, nil
return obj, nil
{{- else }}
{{ $field.EndAction }}
{{- end }}
{{- end }}
{{- end }}{{- end }}
default:
// 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 }}
`))
func generateCode(objects *Metadata) error {
@ -224,15 +332,18 @@ func generateCode(objects *Metadata) error {
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 {
return err
fmt.Println("WARN: could not format source", err)
p = buf.Bytes()
}
_, err = f.Write(p)
return err

View file

@ -17,7 +17,7 @@ import (
func AnalyzeStructure(filex string) error {
fmt.Println("Search...", filex)
files, err := filepath.Glob(filex + "/*.xml")
files, err := filepath.Glob(filex + "/*-legends.xml")
if err != nil {
return err
}
@ -44,12 +44,14 @@ func NewFieldData() *FieldData {
}
type AnalyzeData struct {
Fields map[string]*FieldData
Fields map[string]*FieldData
SubTypes map[string]*map[string]*Subtype
}
func NewAnalyzeData() *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 {
xmlFile, err := os.Open(file)
if err != nil {
fmt.Println(err)
func (a *AnalyzeData) GetSubType(s string, t string) *Subtype {
var (
st *map[string]*Subtype
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)
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 = "|"
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 {
s := strings.Join(path, PATH_SEPARATOR)
fd := a.GetField(s)
if plus {
if ctx.plus {
fd.Plus = true
} else {
fd.Base = true
@ -112,7 +171,11 @@ func analyzeElement(d *xml.Decoder, a *AnalyzeData, path []string, plus bool) er
}
var (
data []byte
data []byte
id int
idFound bool
subtype string
subtypeFound bool
)
value := true
@ -124,7 +187,7 @@ Loop:
if err == io.EOF {
break Loop
} else if err != nil {
return err
return nil, err
}
switch t := tok.(type) {
case xml.StartElement:
@ -137,24 +200,64 @@ Loop:
}
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:
data = append(data, t...)
case xml.EndElement:
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
}
return &Value{Name: t.Name.Local, Value: s}, nil
}
if t.Name.Local == "type" {
path[len(path)-2] = path[len(path)-2] + "+" + strcase.ToCamel(string(data))
}
return nil
return nil, err
}
}
return nil
return nil, nil
}

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,10 @@ package model
import (
"encoding/xml"
"fmt"
"log"
"os"
"strconv"
"strings"
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
)
@ -23,6 +26,8 @@ func Parse(file string) (*DfWorld, error) {
converter := util.NewConvertReader(xmlFile)
d := xml.NewDecoder(converter)
var world *DfWorld
BaseLoop:
for {
tok, err := d.Token()
if err != nil {
@ -31,10 +36,51 @@ func Parse(file string) (*DfWorld, error) {
switch t := tok.(type) {
case xml.StartElement:
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)) {
@ -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))
}
}
}
}