diff --git a/ipub/ast/ast.go b/ipub/ast/ast.go index 2a8823f..b5813ca 100644 --- a/ipub/ast/ast.go +++ b/ipub/ast/ast.go @@ -36,6 +36,7 @@ type Element interface { InsertBefore(self, v1, insertee Element) InsertAfter(self, v1, insertee Element) + xml.Marshaler xml.Unmarshaler } @@ -182,7 +183,7 @@ func (e *BaseElement) InsertBefore(self, v1, insertee Element) { } } -func (e *BaseElement) UnmarshalChildren(self Element, d *xml.Decoder, start xml.StartElement) error { +func (e *BaseElement) UnmarshalXMLElement(self Element, d *xml.Decoder, start xml.StartElement) error { elErr := fmt.Errorf("unable to unmarshal element kind %q", self.Kind()) if n := self.Name(); n != (xml.Name{}) { @@ -232,6 +233,38 @@ func (e *BaseElement) UnmarshalChildren(self Element, d *xml.Decoder, start xml. } } +func (e *BaseElement) MarshalXMLElement(self Element, enc *xml.Encoder, start xml.StartElement) error { + elErr := fmt.Errorf("unable to marshal element kind %q", self.Kind()) + + if n := self.Name(); n != (xml.Name{}) { + start.Name = self.Name() + } + + ka, err := self.Kind().MarshalXMLAttr(elementKindAttrName) + if err != nil { + return errors.Join(elErr, err) + } + + start.Attr = append(start.Attr, ka) + + data := struct { + Children []Element `xml:",any"` + }{Children: []Element{}} + + for c := self.FirstChild(); c != nil; { + data.Children = append(data.Children, c) + c = c.NextSibling() + } + + err = enc.EncodeElement(&data, start) + if err != nil && err != io.EOF { + return err + return errors.Join(elErr, err) + } + + return nil +} + func ensureIsolated(e Element) { if p := e.Parent(); p != nil { p.RemoveChild(p, e) diff --git a/ipub/ast/content.go b/ipub/ast/content.go index d5e236d..a6d06ea 100644 --- a/ipub/ast/content.go +++ b/ipub/ast/content.go @@ -19,7 +19,11 @@ func (e Content) Kind() ElementKind { return KindContent } -func (n *Content) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - return n.UnmarshalChildren(n, d, start) +func (e *Content) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + return e.MarshalXMLElement(e, enc, start) +} + +func (e *Content) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) error { + return e.UnmarshalXMLElement(e, dec, start) } diff --git a/ipub/ast/errors.go b/ipub/ast/errors.go new file mode 100644 index 0000000..06ee830 --- /dev/null +++ b/ipub/ast/errors.go @@ -0,0 +1,36 @@ +package ast + +import ( + "encoding/xml" + "fmt" +) + +type ErrInvalidAttrName struct { + Actual xml.Name + Expected xml.Name +} + +var _ error = ErrInvalidAttrName{} + +func (err ErrInvalidAttrName) Error() string { + return fmt.Sprintf("attribute %q has invalid name, expected %q", fmtXMLName(err.Expected), fmtXMLName(err.Actual)) +} + +type ErrInvalidAttrValue struct { + Attr xml.Attr + Message string +} + +var _ error = ErrInvalidAttrValue{} + +func (err ErrInvalidAttrValue) 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/ast/section.go b/ipub/ast/section.go index 89134a5..5b74c88 100644 --- a/ipub/ast/section.go +++ b/ipub/ast/section.go @@ -5,7 +5,8 @@ import ( ) type Section struct { - Body Body `xml:"body"` + XMLName xml.Name `xml:"html"` + Body *Body `xml:"body"` } type Body struct { @@ -22,6 +23,10 @@ func (e Body) Kind() ElementKind { return KindBody } -func (n *Body) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { - return n.UnmarshalChildren(n, d, start) +func (e *Body) MarshalXML(enc *xml.Encoder, start xml.StartElement) error { + return e.MarshalXMLElement(e, enc, start) +} + +func (e *Body) UnmarshalXML(dec *xml.Decoder, start xml.StartElement) error { + return e.UnmarshalXMLElement(e, dec, start) }