package jdr
import (
"encoding/xml"
"html/template"
"errors"
"log"
"os"
)
// Page represents a page of a character sheet.
type Page struct {
Items []SheetItem
}
// ToHTML transforms a Page into a HTML code.
// This HTML code is embeddable into the character sheet webpage
func (page *Page) ToHTML() template.HTML {
ret := ""
for _, item := range page.Items {
ret += string(item.ToHTML())
}
return template.HTML(ret)
}
// SheetItem represents an item in the character sheet (either a structural element or a value)
type SheetItem interface {
Name() string
ToHTML() template.HTML
}
// Block represents a SheetItem that contains other SheetItems.
// It is used as a structural element to group values together.
type Block struct {
name string
Items []SheetItem
}
// Name of the block (possibly empty if the block is unnamed)
func (block *Block) Name() string {
return block.name
}
// ToHTML transforms a Block into a HTML code.
func (block *Block) ToHTML() template.HTML {
begin := `
`
ret := template.HTML(begin)
if block.Name() != "" {
ret += template.HTML(legend)
}
for _, item := range block.Items {
ret += item.ToHTML()
}
ret += template.HTML(end)
return ret
}
// Variable is a value in the character sheet, such as the name of the character, or its XP count
type Variable struct {
Type VariableType
name string
}
// Name of the variable
func (v *Variable) Name() string {
return v.name
}
// ToHTML transforms a Variable into a HTML code
func (v *Variable) ToHTML() template.HTML {
label := ""
if v.name != "" {
label = ``
}
html, err := v.Type.ToHTML(v.Type.DefaultValue())
if err != nil {
log.Fatal(err)
}
return template.HTML(label) + html + template.HTML(` `)
}
// CharacterSheet is a role-play character sheet.
// It is structured in Pages, Blocks and Variables
type CharacterSheet struct {
types map[string]VariableType
Pages []*Page
}
// Script renders the Javascript code necessary to render the character sheet web page interactive
func (cs *CharacterSheet) Script() template.JS {
return template.JS("")
}
// Render the CharacterSheet into an embeddable HTML code
func (cs *CharacterSheet) Render() template.HTML {
ret := template.HTML("")
for _, page := range cs.Pages {
ret += page.ToHTML()
}
return ret
}
// AddType adds a custom variable type to the character sheet
func (cs *CharacterSheet) AddType(t VariableType) {
//create the type array if it doesn't exist yet
if cs.types == nil {
cs.types = make(map[string]VariableType)
}
name := t.Name()
cs.types[name] = t
}
// AddDefaultTypes adds the default variable types to the character sheet
func (cs *CharacterSheet) AddDefaultTypes() {
cs.AddType(&VariableType_bool{})
cs.AddType(&VariableType_int{})
cs.AddType(&VariableType_float{})
cs.AddType(&VariableType_string{})
cs.AddType(&VariableType_d{})
}
// getType searches into the types (default & customs) of a character sheet for one matching "typename"
func (cs *CharacterSheet) getType(typename string) (VariableType, error) {
t, ok := cs.types[typename]
if !ok {
return &VariableType_badType{}, errors.New("unknown type \"" + typename + "\"")
}
return t, nil
}
// xmlNode is a temporary structure used for parsing the character sheet XML file
// without knowing its structure à priori
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
}
// ReadCharacterSheet opens a character sheet XML file
// and parses the XML to get a CharacterSheet struct
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
}