From 7f6f9f768282917ca472717eb3d54bd04d915609 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L de Mello" Date: Thu, 22 May 2025 11:08:18 -0300 Subject: [PATCH] feat(attr,ipub): Element interface and ElementKind to prepare unmarshalling of un-structured childre --- ipub/element/attr/errors.go | 36 +++++++++++++++++++++ ipub/element/element.go | 62 +++++++++++++++++++++++++++++++++++++ ipub/element/sections.go | 17 ++++++++++ 3 files changed, 115 insertions(+) create mode 100644 ipub/element/attr/errors.go create mode 100644 ipub/element/element.go diff --git a/ipub/element/attr/errors.go b/ipub/element/attr/errors.go new file mode 100644 index 0000000..4612549 --- /dev/null +++ b/ipub/element/attr/errors.go @@ -0,0 +1,36 @@ +package attr + +import ( + "encoding/xml" + "fmt" +) + +type ErrInvalidName struct { + Actual xml.Name + Expected xml.Name +} + +var _ error = ErrInvalidName{} + +func (err ErrInvalidName) Error() string { + return fmt.Sprintf("attribute %q has invalid name, expected %q", FmtXMLName(err.Actual), FmtXMLName(err.Expected)) +} + +type ErrInvalidValue struct { + Attr xml.Attr + Message string +} + +var _ error = ErrInvalidValue{} + +func (err ErrInvalidValue) Error() string { + return fmt.Sprintf("attribute %q's value %q is invalid: %s", FmtXMLName(err.Attr.Name), err.Attr.Value, err.Message) +} + +func FmtXMLName(n xml.Name) string { + s := n.Local + if n.Space != "" { + s = fmt.Sprintf("%s:%s", n.Space, n.Local) + } + return s +} diff --git a/ipub/element/element.go b/ipub/element/element.go new file mode 100644 index 0000000..3eb09b5 --- /dev/null +++ b/ipub/element/element.go @@ -0,0 +1,62 @@ +package element + +import ( + "encoding/xml" + "errors" + "fmt" + "strings" + + "forge.capytal.company/capytalcode/project-comicverse/ipub/element/attr" +) + +type Element interface { + Kind() ElementKind +} + +type ElementKind string + +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"} +) diff --git a/ipub/element/sections.go b/ipub/element/sections.go index 0e2a2b3..ae94204 100644 --- a/ipub/element/sections.go +++ b/ipub/element/sections.go @@ -7,11 +7,23 @@ type Section struct { Body Body `xml:"body"` } +var KindSection = NewElementKind("section", Section{}) + +func (Section) Kind() ElementKind { + return KindSection +} + type Body struct { XMLName xml.Name `xml:"body"` Test string `xml:"test,attr"` } +var KindBody = NewElementKind("body", Body{}) + +func (Body) Kind() ElementKind { + return KindBody +} + type Paragraph struct { XMLName xml.Name `xml:"p"` DataElement ElementKind `xml:"data-ipub-element,attr"` @@ -20,3 +32,8 @@ type Paragraph struct { Text string `xml:",chardata"` } +var KindParagraph = NewElementKind("paragraph", Paragraph{}) + +func (Paragraph) Kind() ElementKind { + return KindParagraph +}