113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
package element
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
|
|
"code.capytal.cc/capytal/comicverse/ipub/element/attr"
|
|
)
|
|
|
|
type Element interface {
|
|
Kind() ElementKind
|
|
}
|
|
|
|
type ElementChildren []Element
|
|
|
|
func (ec *ElementChildren) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
elErr := fmt.Errorf("unable to unsmarshal element %q", attr.FmtXMLName(start.Name))
|
|
|
|
i := slices.IndexFunc(start.Attr, func(a xml.Attr) bool {
|
|
return a.Name == elementKindAttrName
|
|
})
|
|
if i == -1 {
|
|
return errors.Join(elErr, fmt.Errorf("element kind not specified"))
|
|
}
|
|
|
|
var k ElementKind
|
|
if err := k.UnmarshalXMLAttr(start.Attr[i]); err != nil {
|
|
return err
|
|
}
|
|
|
|
ks := elementKindList[k]
|
|
|
|
// Get a pointer of a new instance of the underlying implementation so we can
|
|
// change it without manipulating the value inside the elementKindList.
|
|
ep := reflect.New(reflect.TypeOf(ks))
|
|
if ep.Elem().Kind() == reflect.Pointer {
|
|
// If the implementation is a pointer, we need the underlying value so we can
|
|
// manipulate it.
|
|
ep = reflect.New(reflect.TypeOf(ks).Elem())
|
|
}
|
|
|
|
if err := d.DecodeElement(ep.Interface(), &start); err != nil && err != io.EOF {
|
|
return errors.Join(elErr, err)
|
|
}
|
|
|
|
if ec == nil {
|
|
c := ElementChildren{}
|
|
ec = &c
|
|
}
|
|
|
|
s := *ec
|
|
s = append(s, ep.Interface().(Element))
|
|
*ec = s
|
|
|
|
return nil
|
|
}
|
|
|
|
type ElementKind string
|
|
|
|
// NewElementKind registers a new Element implementation to a private list which is
|
|
// consumed bu [ElementChildren] to properly find what underlying type is a children
|
|
// of another element struct.
|
|
func NewElementKind(n string, s Element) ElementKind {
|
|
k := ElementKind(n)
|
|
|
|
if _, ok := elementKindList[k]; ok {
|
|
panic(fmt.Sprintf("element kind %q already registered", n))
|
|
}
|
|
|
|
elementKindList[k] = s
|
|
return k
|
|
}
|
|
|
|
func (k ElementKind) MarshalXMLAttr(n xml.Name) (xml.Attr, error) {
|
|
if n != elementKindAttrName {
|
|
return xml.Attr{}, attr.ErrInvalidName{Actual: n, Expected: elementKindAttrName}
|
|
}
|
|
return xml.Attr{Name: elementKindAttrName, Value: k.String()}, nil
|
|
}
|
|
|
|
func (k *ElementKind) UnmarshalXMLAttr(a xml.Attr) error {
|
|
ak := ElementKind(a.Value)
|
|
|
|
if _, ok := elementKindList[ak]; !ok {
|
|
v := make([]string, 0, len(elementKindList))
|
|
for k := range elementKindList {
|
|
v = append(v, k.String())
|
|
}
|
|
return attr.ErrInvalidValue{
|
|
Attr: a,
|
|
Message: fmt.Sprintf("must be a registered element (%q)", strings.Join(v, `", "`)),
|
|
}
|
|
}
|
|
|
|
*k = ak
|
|
|
|
return nil
|
|
}
|
|
|
|
func (k ElementKind) String() string {
|
|
return string(k)
|
|
}
|
|
|
|
var (
|
|
elementKindList = make(map[ElementKind]Element)
|
|
elementKindAttrName = xml.Name{Local: "data-ipub-element"}
|
|
)
|