dorfylegends/analyze/df/structure.go
2022-05-03 19:39:00 +00:00

349 lines
6.5 KiB
Go

package df
import (
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/cheggaaa/pb/v3"
"github.com/iancoleman/strcase"
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
)
func AnalyzeStructure(filex string, a *AnalyzeData) error {
fmt.Println("Search...", filex)
files, err := filepath.Glob(filex + "/*-legends.xml")
if err != nil {
return err
}
fmt.Println(files)
if a == nil {
a = NewAnalyzeData()
}
for _, file := range files {
analyze(file, a)
}
return a.Save()
}
type FieldData struct {
IsString bool
NoBool bool
Multiple bool
Base bool
Plus bool
Values map[string]bool
Enum bool
}
func NewFieldData() *FieldData {
return &FieldData{
Enum: true,
Values: make(map[string]bool),
}
}
type AnalyzeData struct {
Fields map[string]*FieldData
SubTypes map[string]*map[string]*Subtype
Overwrites *Overwrites `json:"-"`
}
func NewAnalyzeData() *AnalyzeData {
return &AnalyzeData{
Fields: make(map[string]*FieldData, 0),
SubTypes: make(map[string]*map[string]*Subtype),
}
}
func (a *AnalyzeData) Save() error {
file, err := json.MarshalIndent(a, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile("analyze.json", file, 0644)
}
func LoadAnalyzeData() (*AnalyzeData, error) {
data, err := ioutil.ReadFile("analyze.json")
if err != nil {
return nil, err
}
a := NewAnalyzeData()
json.Unmarshal(data, a)
data, err = ioutil.ReadFile("overwrites.json")
if err != nil {
return nil, err
}
overwrites := &Overwrites{}
json.Unmarshal(data, overwrites)
a.Overwrites = overwrites
return a, nil
}
func (a *AnalyzeData) GetField(s string) *FieldData {
if f, ok := a.Fields[s]; ok {
return f
} else {
f := NewFieldData()
a.Fields[s] = f
return f
}
}
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
}
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
overwrites *Overwrites
}
type AdditionalField struct {
Name string
Type string
}
type Overwrites struct {
ForceEnum map[string]bool
AdditionalFields map[string][]AdditionalField
Relations map[string]string
}
func analyze(file string, a *AnalyzeData) error {
data, err := ioutil.ReadFile("overwrites.json")
if err != nil {
return err
}
overwrites := &Overwrites{}
json.Unmarshal(data, overwrites)
ctx := AnalyzeContext{
file: file,
plus: false,
subtypes: make(map[string]map[int]string),
overwrites: overwrites,
}
// base file
fi, err := os.Stat(file)
if err != nil {
return err
}
size := fi.Size()
bar := pb.Full.Start64(size)
xmlFile, err := os.Open(file)
if err != nil {
return err
}
fmt.Println("\nAnalyzing", file)
defer xmlFile.Close()
converter := util.NewConvertReader(xmlFile)
barReader := bar.NewProxyReader(converter)
_, err = analyzeElement(xml.NewDecoder(barReader), a, make([]string, 0), &ctx)
if err != nil {
return err
}
bar.Finish()
// plus file
ctx.plus = true
file = strings.Replace(file, "-legends.xml", "-legends_plus.xml", 1)
fi, err = os.Stat(file)
if err != nil {
return err
}
size = fi.Size()
bar = pb.Full.Start64(size)
xmlFile, err = os.Open(file)
if err != nil {
return err
}
fmt.Println("\nAnalyzing", file)
defer xmlFile.Close()
converter = util.NewConvertReader(xmlFile)
barReader = bar.NewProxyReader(converter)
_, err = analyzeElement(xml.NewDecoder(barReader), a, make([]string, 0), &ctx)
bar.Finish()
return err
}
const PATH_SEPARATOR = "|"
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 ctx.plus {
fd.Plus = true
} else {
fd.Base = true
}
}
var (
data []byte
id int
idFound bool
subtype string
subtypeFound bool
)
value := true
fields := make(map[string]bool)
Loop:
for {
tok, err := d.Token()
if err == io.EOF {
break Loop
} else if err != nil {
return nil, err
}
switch t := tok.(type) {
case xml.StartElement:
value = false
newPath := append(path, t.Name.Local)
if _, ok := fields[t.Name.Local]; ok {
a.GetField(strings.Join(newPath, PATH_SEPARATOR)).Multiple = true
}
fields[t.Name.Local] = true
v, err := analyzeElement(d, a, newPath, ctx)
if err != nil {
return nil, err
}
if v != nil {
if v.Name == "id" || v.Name == "local_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
}
}
}
if allowedTyped[strings.Join(path, PATH_SEPARATOR)] {
path[len(path)-1] = path[len(path)-1] + "+" + strcase.ToCamel(subtype)
}
}
}
case xml.CharData:
data = append(data, t...)
case xml.EndElement:
if value {
s := strings.TrimSpace(string(data))
if _, err := strconv.Atoi(s); err != nil {
f := a.GetField(strings.Join(path, PATH_SEPARATOR))
force := ctx.overwrites.ForceEnum[strings.Join(path, PATH_SEPARATOR)]
if force {
f.Enum = true
}
f.IsString = true
if s != "" && f.Enum {
f.Values[s] = true
}
if len(f.Values) > 30 && !force {
f.Values = make(map[string]bool)
f.Enum = false
}
}
if len(s) > 0 {
a.GetField(strings.Join(path, PATH_SEPARATOR)).NoBool = true
}
return &Value{Name: t.Name.Local, Value: s}, nil
}
return nil, err
}
}
return nil, nil
}