16 Commits

Author SHA1 Message Date
9445a7c4cd Use built-in xml attributes 2024-10-17 21:44:19 +02:00
42416d13e7 Bring back proper extensionAttributes 2024-10-17 21:27:54 +02:00
b70ff82141 Delete extensionAttribute.go and simplify undefined attributes 2024-10-17 21:08:41 +02:00
3172a4865a Create needed functions for common attributes and their extensions 2024-10-17 20:48:32 +02:00
3b3f1f7e41 Change title 2024-10-17 20:47:37 +02:00
9c38048bd2 Properly use xml names 2024-10-17 20:10:18 +02:00
26e0c99150 Use *XHTMLDiv 2024-10-17 19:44:27 +02:00
6117876a59 Check uris before applying them 2024-10-17 19:28:09 +02:00
cb61d90cae Make NewLogo more flexible 2024-10-17 19:19:22 +02:00
30623fdfe0 More ",chardata" 2024-10-17 19:14:37 +02:00
4ae8cecb17 More ",chardata" 2024-10-17 19:11:33 +02:00
8e226b48ef isValidXML is no longer of any use 2024-10-17 19:05:23 +02:00
478d679985 Small bug fix 2024-10-17 18:59:24 +02:00
d11b229691 No more undefined content in link and category
It seems that undefined content is only mentioned in RFC4287 because
link and category are elements and those usually need some content.
2024-10-17 18:57:43 +02:00
e73b78ef30 Content can also be empty 2024-10-17 18:40:52 +02:00
5a82f1799f Force undefined content of category and link to be empty or valid xml 2024-10-17 18:39:57 +02:00
19 changed files with 123 additions and 97 deletions

View File

@ -1,4 +1,3 @@
# atom-feed # atom
An extensible implementation of an Atom feed that aims to be very close to RFC4287. An extensible implementation of an Atom feed that aims to be very close to RFC4287.

View File

@ -82,6 +82,12 @@ func isValidLanguageTag(tag LanguageTag) bool {
return err == nil return err == nil
} }
// isValidAttribute checks whether an Attribute is valid. It returns a bool.
func isValidAttribute(attribute string) bool {
regex := regexp.MustCompile(`^[a-zA-Z0-9_]+="[^"]*"$`)
return regex.MatchString(attribute)
}
// NewURN generates an new valid IRI based on a UUID. It returns an IRI. // NewURN generates an new valid IRI based on a UUID. It returns an IRI.
func NewURN() IRI { func NewURN() IRI {
return IRI(fmt.Sprint("urn:uuid:", uuid.New())) return IRI(fmt.Sprint("urn:uuid:", uuid.New()))

View File

@ -1,22 +1,23 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"html" "html"
) )
type Category struct { type Category struct {
XMLName xml.Name `xml:"category"`
*CommonAttributes *CommonAttributes
Content string `xml:",chardata"` // undefinedContent in RFC4287 Term string `xml:"term,attr"`
Term string `xml:"term,attr"` Scheme IRI `xml:"scheme,attr,omitempty"`
Scheme IRI `xml:"scheme,attr,omitempty"` Label string `xml:"label,attr,omitempty"`
Label string `xml:"label,attr,omitempty"`
} }
// NewCategory creates a new Category. It returns a *Category and an error. // NewCategory creates a new Category. It returns a *Category.
func NewCategory(term, content string) (*Category, error) { func NewCategory(term string) *Category {
return &Category{Term: term, Content: content}, nil return &Category{Term: term}
} }
// SetLabel sets the label of the Category. // SetLabel sets the label of the Category.
@ -41,9 +42,5 @@ func (c *Category) Check() error {
return fmt.Errorf("label attribute %v of category not correctly escaped", c.Label) return fmt.Errorf("label attribute %v of category not correctly escaped", c.Label)
} }
if c.Content == "" {
return errors.New("content element of category empty")
}
return nil return nil
} }

View File

@ -1,21 +1,27 @@
package atom package atom
import "fmt" import (
"encoding/xml"
)
type CommonAttributes struct { type CommonAttributes struct {
Base IRI `xml:"base,attr,omitempty"` Base IRI `xml:"base,attr,omitempty"`
Lang LanguageTag `xml:"lang,attr,omitempty"` Lang LanguageTag `xml:"lang,attr,omitempty"`
UndefinedAttributes []*ExtensionAttribute `xml:",any"` UndefinedAttributes []*xml.Attr `xml:",attr,omitempty"`
} }
// Check checks the CommonAttributes for incompatibilities with RFC4287. It // NewCommonAttributes creates a new set of CommonAttributes. It returns a
// returns an error. // *CommonAttributes.
func (c *CommonAttributes) Check() error { func NewCommonAttributes() *CommonAttributes {
for i, e := range c.UndefinedAttributes { return new(CommonAttributes)
if err := e.Check(); err != nil { }
return fmt.Errorf("extension attribute %v of common attributes: %v", i, err)
} // AddExtensionAttribute adds the ExtensionAttribute to the CommonAttributes.
func (c *CommonAttributes) AddExtensionAttribute(name, value string) {
if c.UndefinedAttributes == nil {
c.UndefinedAttributes = make([]*xml.Attr, 1)
c.UndefinedAttributes[0] = &xml.Attr{Name: xml.Name{Local: name}, Value: value}
} else {
c.UndefinedAttributes = append(c.UndefinedAttributes, &xml.Attr{Name: xml.Name{Local: name}, Value: value})
} }
return nil
} }

View File

@ -1,6 +1,7 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@ -12,16 +13,17 @@ import (
// a non-empty atom:summary element when the entry contains no atom:content // a non-empty atom:summary element when the entry contains no atom:content
// element. // element.
type Entry struct { type Entry struct {
XMLName xml.Name `xml:"entry"`
*CommonAttributes *CommonAttributes
Authors []*Person `xml:"author,omitempty"` Authors []*Person `xml:"author,omitempty"`
Categories []*Category `xml:"category,omitempty"` Categories []*Category `xml:",omitempty"`
Content Content `xml:"content,omitempty"` Content Content `xml:",omitempty"`
Contributors []*Person `xml:"contributors,omitempty"` Contributors []*Person `xml:"contributors,omitempty"`
ID *ID `xml:"id"` ID *ID
Links []*Link `xml:"link,omitempty"` Links []*Link `xml:",omitempty"`
Published *Date `xml:"published,omitempty"` Published *Date `xml:"published,omitempty"`
Rights Text `xml:"rights,omitempty"` Rights Text `xml:"rights,omitempty"`
Source *Source `xml:"source,omitempty"` Source *Source `xml:",omitempty"`
Summary Text `xml:"summary,omitempty"` Summary Text `xml:"summary,omitempty"`
Title Text `xml:"title"` Title Text `xml:"title"`
Updated *Date `xml:"updated"` Updated *Date `xml:"updated"`
@ -62,8 +64,13 @@ func NewEntry(title string) (*Entry, error) {
return nil, fmt.Errorf("error creating new entry: %v", err) return nil, fmt.Errorf("error creating new entry: %v", err)
} }
id, err := NewID(NewURN())
if err != nil {
return nil, fmt.Errorf("error creating new entry: %v", err)
}
return &Entry{ return &Entry{
ID: NewID(NewURN()), ID: id,
Title: text, Title: text,
Updated: NewDate(time.Now()), Updated: NewDate(time.Now()),
}, nil }, nil

View File

@ -1,21 +0,0 @@
package atom
import (
"encoding/xml"
"errors"
)
type ExtensionAttribute struct {
Value any `xml:",attr"`
XMLName xml.Name
}
// Check checks the ExtensionAttribute for incompatibilities with RFC4287. It
// returns an error.
func (e *ExtensionAttribute) Check() error {
if e.Value == nil {
return errors.New("value element of extension attribute empty")
}
return nil
}

25
feed.go
View File

@ -10,20 +10,20 @@ import (
type Feed struct { type Feed struct {
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"` XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
*CommonAttributes *CommonAttributes
Authors []*Person `xml:"author,omitempty"` Authors []*Person `xml:"author,omitempty"`
Categories []*Category `xml:"category,omitempty"` Categories []*Category `xml:",omitempty"`
Contributors []*Person `xml:"contributor,omitempty"` Contributors []*Person `xml:"contributor,omitempty"`
Generator *Generator `xml:"generator,omitempty"` Generator *Generator `xml:",omitempty"`
Icon *Icon `xml:"icon,omitempty"` Icon *Icon `xml:",omitempty"`
ID *ID `xml:"id"` ID *ID
Links []*Link `xml:"link,omitempty"` Links []*Link `xml:",omitempty"`
Logo *Logo `xml:"logo,omitempty"` Logo *Logo `xml:",omitempty"`
Rights Text `xml:"rights,omitempty"` Rights Text `xml:"rights,omitempty"`
Subtitle Text `xml:"subtitle,omitempty"` Subtitle Text `xml:"subtitle,omitempty"`
Title Text `xml:"title"` Title Text `xml:"title"`
Updated *Date `xml:"updated"` Updated *Date `xml:"updated"`
Extensions []*ExtensionElement `xml:",any,omitempty"` Extensions []*ExtensionElement `xml:",any,omitempty"`
Entries []*Entry `xml:"entry,omitempty"` Entries []*Entry `xml:",omitempty"`
} }
// NewFeed creates a new Feed. It returns a *Feed and an error. // NewFeed creates a new Feed. It returns a *Feed and an error.
@ -33,8 +33,13 @@ func NewFeed(title string) (*Feed, error) {
return nil, fmt.Errorf("error creating new feed: %v", err) return nil, fmt.Errorf("error creating new feed: %v", err)
} }
id, err := NewID(NewURN())
if err != nil {
return nil, fmt.Errorf("error creating new feed: %v", err)
}
return &Feed{ return &Feed{
ID: NewID(NewURN()), ID: id,
Title: text, Title: text,
Updated: NewDate(time.Now()), Updated: NewDate(time.Now()),
}, nil }, nil

View File

@ -1,16 +1,18 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"html" "html"
) )
type Generator struct { type Generator struct {
XMLName xml.Name `xml:"generator"`
*CommonAttributes *CommonAttributes
URI IRI `xml:"uri,attr,omitempty"` URI IRI `xml:"uri,attr,omitempty"`
Version string `xml:"version,attr,omitempty"` Version string `xml:"version,attr,omitempty"`
Text string `xml:"text"` Text string `xml:",chardata"`
} }
// NewGenerator creates a new Generator. It returns a *Generator. // NewGenerator creates a new Generator. It returns a *Generator.

14
icon.go
View File

@ -1,18 +1,24 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
) )
type Icon struct { type Icon struct {
XMLName xml.Name `xml:"icon"`
*CommonAttributes *CommonAttributes
URI IRI `xml:"uri"` URI IRI `xml:",chardata"`
} }
// NewIcon creates a new Icon. It returns a *Icon. // NewIcon creates a new Icon. It returns a *Icon and an error.
func NewIcon(uri string) *Icon { func NewIcon(uri IRI) (*Icon, error) {
return &Icon{URI: IRI(uri)} if !isValidIRI(uri) {
return nil, fmt.Errorf("uri %v not correctly formatted", uri)
}
return &Icon{URI: uri}, nil
} }
// Check checks the Icon for incompatibilities with RFC4287. It returns an // Check checks the Icon for incompatibilities with RFC4287. It returns an

12
id.go
View File

@ -1,18 +1,24 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
) )
type ID struct { type ID struct {
XMLName xml.Name `xml:"id"`
*CommonAttributes *CommonAttributes
URI IRI `xml:",chardata"` URI IRI `xml:",chardata"`
} }
// NewID creates a new ID. It returns a *ID. // NewID creates a new ID. It returns a *ID and an error.
func NewID(id IRI) *ID { func NewID(uri IRI) (*ID, error) {
return &ID{URI: IRI(id)} if !isValidIRI(uri) {
return nil, fmt.Errorf("uri %v not correctly formatted", uri)
}
return &ID{URI: IRI(uri)}, nil
} }
// Check checks the ID for incompatibilities with RFC4287. It returns an error. // Check checks the ID for incompatibilities with RFC4287. It returns an error.

View File

@ -1,12 +1,14 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"mime" "mime"
) )
type InlineOtherContent struct { type InlineOtherContent struct {
XMLName xml.Name `xml:"content"`
*CommonAttributes *CommonAttributes
AnyElement any `xml:",chardata"` AnyElement any `xml:",chardata"`
Type MediaType `xml:"type,attr,omitempty"` Type MediaType `xml:"type,attr,omitempty"`

View File

@ -1,11 +1,13 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
) )
type InlineTextContent struct { type InlineTextContent struct {
XMLName xml.Name `xml:"content"`
*CommonAttributes *CommonAttributes
Type string `xml:"type,attr,omitempty"` // Must be text or html Type string `xml:"type,attr,omitempty"` // Must be text or html
Text string `xml:",chardata"` Text string `xml:",chardata"`

View File

@ -1,14 +1,16 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
) )
type InlineXHTMLContent struct { type InlineXHTMLContent struct {
XMLName xml.Name `xml:"content"`
*CommonAttributes *CommonAttributes
XHTMLDiv *XHTMLDiv
Type string `xml:"type,attr"` Type string `xml:"type,attr"`
XHTMLDiv XHTMLDiv
} }
// newInlineXHTMLContent creates a new InlineXHTMLContent. It returns a // newInlineXHTMLContent creates a new InlineXHTMLContent. It returns a
@ -18,7 +20,7 @@ func newInlineXHTMLContent(mediaType string, content any) (*InlineXHTMLContent,
return nil, fmt.Errorf("media type %v incompatible with inline xhtml content", mediaType) return nil, fmt.Errorf("media type %v incompatible with inline xhtml content", mediaType)
} }
xhtmlDiv, ok := content.(XHTMLDiv) xhtmlDiv, ok := content.(*XHTMLDiv)
if !ok { if !ok {
return nil, fmt.Errorf("content type %T incompatible with inline xhtml content", content) return nil, fmt.Errorf("content type %T incompatible with inline xhtml content", content)
} }

13
link.go
View File

@ -1,15 +1,16 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
) )
type Link struct { type Link struct {
XMLName xml.Name `xml:"link"`
*CommonAttributes *CommonAttributes
Title string `xml:"title,attr,omitempty"` Title string `xml:"title,attr,omitempty"`
Content string `xml:",chardata"` // undefinedContent in RFC4287
Href IRI `xml:"href,attr"` Href IRI `xml:"href,attr"`
Rel string `xml:"rel,attr,omitempty"` Rel string `xml:"rel,attr,omitempty"`
Type MediaType `xml:"type,attr,omitempty"` Type MediaType `xml:"type,attr,omitempty"`
@ -17,9 +18,9 @@ type Link struct {
Length uint `xml:"length,attr,omitempty"` Length uint `xml:"length,attr,omitempty"`
} }
// NewLink creates a new Link. It returns a *Link and an error. // NewLink creates a new Link. It returns a *Link.
func NewLink(href, content string) (*Link, error) { func NewLink(href string) *Link {
return &Link{Href: IRI(href), Content: content}, nil return &Link{Href: IRI(href)}
} }
// Check checks the Link for incompatibilities with RFC4287. It returns an // Check checks the Link for incompatibilities with RFC4287. It returns an
@ -51,10 +52,6 @@ func (l *Link) Check() error {
} }
} }
if l.Content == "" {
return errors.New("content element of link empty")
}
return nil return nil
} }

14
logo.go
View File

@ -1,20 +1,24 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"github.com/google/uuid"
) )
type Logo struct { type Logo struct {
XMLName xml.Name `xml:"logo"`
*CommonAttributes *CommonAttributes
URI IRI `xml:"uri"` URI IRI `xml:",chardata"`
} }
// NewLogo creates a new Logo. It returns a *Logo. // NewLogo creates a new Logo. It returns a *Logo.
func NewLogo() *Logo { func NewLogo(uri IRI) (*Logo, error) {
return &Logo{URI: IRI(fmt.Sprint("urn:uuid:", uuid.New()))} if !isValidIRI(uri) {
return nil, fmt.Errorf("uri %v not correctly formatted", uri)
}
return &Logo{URI: uri}, nil
} }
// Check checks the Logo for incompatibilities with RFC4287. It returns an // Check checks the Logo for incompatibilities with RFC4287. It returns an

View File

@ -1,12 +1,14 @@
package atom package atom
import ( import (
"encoding/xml"
"errors" "errors"
"fmt" "fmt"
"mime" "mime"
) )
type OutOfLineContent struct { type OutOfLineContent struct {
XMLName xml.Name `xml:"content"`
*CommonAttributes *CommonAttributes
Type MediaType `xml:"type,attr,omitempty"` Type MediaType `xml:"type,attr,omitempty"`
SRC IRI `xml:"src,attr"` SRC IRI `xml:"src,attr"`

View File

@ -1,17 +1,21 @@
package atom package atom
import "fmt" import (
"encoding/xml"
"fmt"
)
type Source struct { type Source struct {
XMLName xml.Name `xml:"source"`
*CommonAttributes *CommonAttributes
Authors []*Person `xml:"author,omitempty"` Authors []*Person `xml:"author,omitempty"`
Categories []*Category `xml:"category,omitempty"` Categories []*Category `xml:",omitempty"`
Contributors []*Person `xml:"contributor,omitempty"` Contributors []*Person `xml:"contributor,omitempty"`
Generator *Generator `xml:"generator,omitempty"` Generator *Generator `xml:",omitempty"`
Icon *Icon `xml:"icon,omitempty"` Icon *Icon `xml:",omitempty"`
ID *ID `xml:"id,omitempty"` ID *ID `xml:",omitempty"`
Links []*Link `xml:"link,omitempty"` Links []*Link `xml:",omitempty"`
Logo *Logo `xml:"logo,omitempty"` Logo *Logo `xml:",omitempty"`
Rights Text `xml:"rights,omitempty"` Rights Text `xml:"rights,omitempty"`
Subtitle Text `xml:"subtitle,omitempty"` Subtitle Text `xml:"subtitle,omitempty"`
Title Text `xml:"title,omitempty"` Title Text `xml:"title,omitempty"`

View File

@ -20,7 +20,7 @@ func NewText(textType, content string) (Text, error) {
case "xhtml": case "xhtml":
return &XHTMLText{ return &XHTMLText{
Type: textType, Type: textType,
XHTMLDiv: XHTMLDiv{ XHTMLDiv: &XHTMLDiv{
XMLNS: "http://www.w3.org/1999/xhtml", XMLNS: "http://www.w3.org/1999/xhtml",
Content: content, Content: content,
}, },

View File

@ -7,8 +7,8 @@ import (
type XHTMLText struct { type XHTMLText struct {
*CommonAttributes *CommonAttributes
XHTMLDiv *XHTMLDiv
Type string `xml:"type,attr"` // Must be xhtml Type string `xml:"type,attr"` // Must be xhtml
XHTMLDiv XHTMLDiv
} }
// isText checks whether the XHTMLText is a Text. It returns a bool. // isText checks whether the XHTMLText is a Text. It returns a bool.