package jdr import ( "html/template" "encoding/xml" "errors" "os" "log" ) type Page struct { Items []SheetItem } func (self *Page) ToHTML() template.HTML { ret := "" for _, item := range(self.Items) { ret += string(item.ToHTML()) } return template.HTML(ret) } type SheetItem interface { Name() string ToHTML() template.HTML } type Block struct { name string Items []SheetItem } func (self *Block) Name() string { return self.name } func (self *Block) ToHTML() template.HTML { begin := `
` ret := template.HTML(begin) if self.Name() != "" { ret += template.HTML(legend) } for _, item := range(self.Items) { ret += item.ToHTML() } ret += template.HTML(end) return ret } type Variable struct { Type VariableType name string } func (self *Variable) Name() string { return self.name } func (self *Variable) ToHTML() template.HTML { label := "" if self.name != "" { label = `` } html, err := self.Type.ToHTML(self.Type.DefaultValue()) if err != nil { log.Fatal(err) } return template.HTML(label) + html } type CharacterSheet struct { types map[string]VariableType Pages []*Page } func (self *CharacterSheet) Render() template.HTML { ret := template.HTML("") for _, page := range(self.Pages) { ret += page.ToHTML() } return ret } func (self *CharacterSheet) AddType(t VariableType) { if self.types == nil { self.types = make(map[string]VariableType) } name := t.Name() self.types[name] = t } func (self *CharacterSheet) AddDefaultTypes() { self.AddType(&VariableType_bool{}) self.AddType(&VariableType_int{}) self.AddType(&VariableType_float{}) self.AddType(&VariableType_string{}) self.AddType(&VariableType_d{}) } func (self *CharacterSheet) getType(typename string) (VariableType, error) { t, ok := self.types[typename] if !ok { return &VariableType_badType{}, errors.New("unknown type \"" + typename + "\"") } return t, nil } type xmlNode struct { XMLName xml.Name Attrs []xml.Attr `xml:"-"` Content []byte `xml:",innerxml"` Nodes []xmlNode `xml:",any"` } func (n *xmlNode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { n.Attrs = start.Attr type node xmlNode return d.DecodeElement((*node)(n), &start) } func parseVariable(root xmlNode, sheet *CharacterSheet, defaults *Variable) (*Variable, error) { ret := &Variable{} typeOk := false nameOk := false if defaults != nil { switch defaults.Type.(type) { case *VariableType_badType: typeOk = false default: typeOk = true ret.Type = defaults.Type } } for i := 0; i < len(root.Attrs); i++ { attrName := root.Attrs[i].Name.Local attrVal := root.Attrs[i].Value switch attrName { case "type": t, err := sheet.getType(attrVal) if err != nil { return nil, err } ret.Type = t typeOk = true case "name": ret.name = attrVal nameOk = true } } if !nameOk { return nil, errors.New("Unnamed variable") } if !typeOk { return nil, errors.New("No type provided for variable \"" + ret.Name() + "\"") } return ret, nil } func parseBlock(root xmlNode, sheet *CharacterSheet) (*Block, error) { ret := &Block{} for i := 0; i < len(root.Attrs); i++ { attrName := root.Attrs[i].Name.Local attrVal := root.Attrs[i].Value switch attrName { case "name": ret.name = attrVal } } for i := 0; i < len(root.Nodes); i++ { items, err := parseItem(root.Nodes[i], sheet) if err != nil { return nil, err } for _, item := range(items) { ret.Items = append(ret.Items, item) } } return ret, nil } func parseVariableGroup(root xmlNode, sheet *CharacterSheet, defaults *Variable) ([]*Variable, error) { ret := []*Variable{} typeOk := false if defaults == nil { defaults = &Variable{} } else { typeOk = true } for i := 0; i < len(root.Attrs); i++ { attrName := root.Attrs[i].Name.Local attrVal := root.Attrs[i].Value switch attrName { case "default-type": t, err := sheet.getType(attrVal) if err != nil { return nil, err } defaults.Type = t typeOk = true } } if !typeOk { defaults.Type = &VariableType_badType{} } for i := 0; i < len(root.Nodes); i++ { node := root.Nodes[i] switch node.XMLName.Local { case "variable": item, err := parseVariable(node, sheet, defaults) if err != nil { return nil, err } ret = append(ret, item) case "variable-group": items, err := parseVariableGroup(node, sheet, defaults) if err != nil { return nil, err } for j := 0; j < len(items); j++ { ret = append(ret, items[j]) } default: return nil, errors.New("Unknown variable-group item: \"" + node.XMLName.Local + "\"") } } return ret, nil } func parseItem(root xmlNode, sheet *CharacterSheet) ([]SheetItem, error) { switch root.XMLName.Local { case "block": block, err := parseBlock(root, sheet) if err != nil { return nil, err } return []SheetItem{block}, nil case "variable": variable, err := parseVariable(root, sheet, nil) if err != nil { return nil, err } return []SheetItem{variable}, err case "variable-group": variables, err := parseVariableGroup(root, sheet, nil) if err != nil { return nil, err } items := make([]SheetItem, len(variables)) for i, v := range variables { items[i] = SheetItem(v) } return items, nil default: return nil, errors.New("Unknown item: " + root.XMLName.Local) } } func parsePage(root xmlNode, sheet *CharacterSheet) (*Page, error) { ret := &Page{} for i := 0; i < len(root.Nodes); i++ { items, err := parseItem(root.Nodes[i], sheet) if err != nil { return nil, err } for _, item := range(items) { ret.Items = append(ret.Items, item) } } return ret, nil } func parseSheet(root xmlNode) (*CharacterSheet, error) { if root.XMLName.Local != "jdr-desc" { return nil, errors.New("Wrong root node") } sheet := &CharacterSheet{} sheet.AddDefaultTypes() for i := 0; i < len(root.Nodes); i++ { node := root.Nodes[i] switch node.XMLName.Local { case "custom-types": err := parseCustomTypes(node, sheet) if err != nil { return nil, err } case "page": page, err := parsePage(node, sheet) if err != nil { return nil, err } sheet.Pages = append(sheet.Pages, page) default: return nil, errors.New("Wrong format") } } return sheet, nil } func ReadCharacterSheet(filename string) (*CharacterSheet, error) { file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() decoder := xml.NewDecoder(file) var n xmlNode err = decoder.Decode(&n) if err != nil { return nil, err } sheet, err := parseSheet(n) if err != nil { return nil, err } return sheet, nil }