2024-10-13 17:19:40 +02:00
|
|
|
package atomfeed
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/xml"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2024-10-15 17:29:22 +02:00
|
|
|
"time"
|
2024-10-13 17:19:40 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type Feed struct {
|
|
|
|
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
|
|
|
|
*CommonAttributes
|
|
|
|
Authors []*Person `xml:"author,omitempty"`
|
|
|
|
Categories []*Category `xml:"category,omitempty"`
|
|
|
|
Contributors []*Person `xml:"contributor,omitempty"`
|
|
|
|
Generator *Generator `xml:"generator,omitempty"`
|
|
|
|
Icon *Icon `xml:"icon,omitempty"`
|
|
|
|
ID *ID `xml:"id"`
|
2024-10-15 17:34:33 +02:00
|
|
|
Links []*Link `xml:"link,omitempty"`
|
2024-10-13 17:19:40 +02:00
|
|
|
Logo *Logo `xml:"logo,omitempty"`
|
2024-10-15 17:29:22 +02:00
|
|
|
Rights Text `xml:"rights,omitempty"`
|
|
|
|
Subtitle Text `xml:"subtitle,omitempty"`
|
|
|
|
Title Text `xml:"title"`
|
2024-10-13 17:19:40 +02:00
|
|
|
Updated *Date `xml:"updated"`
|
2024-10-13 20:42:17 +02:00
|
|
|
Extensions []*ExtensionElement `xml:",any,omitempty"`
|
2024-10-13 17:19:40 +02:00
|
|
|
Entries []*Entry `xml:"entry,omitempty"`
|
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// NewFeed creates a new Feed. It returns a *Feed and an error.
|
2024-10-15 17:36:56 +02:00
|
|
|
func NewFeed(title string) (*Feed, error) {
|
2024-10-15 17:29:22 +02:00
|
|
|
text, err := NewText("text", title)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error creating new feed: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Feed{
|
|
|
|
ID: NewID(),
|
|
|
|
Title: text,
|
|
|
|
Updated: NewDate(time.Now()),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// AddAuthor adds the Person as an author to the Feed.
|
2024-10-15 17:29:22 +02:00
|
|
|
func (f *Feed) AddAuthor(p *Person) {
|
|
|
|
if f.Authors == nil {
|
|
|
|
f.Authors = make([]*Person, 1)
|
|
|
|
f.Authors[0] = p
|
|
|
|
} else {
|
|
|
|
f.Authors = append(f.Authors, p)
|
|
|
|
}
|
2024-10-15 17:39:17 +02:00
|
|
|
|
|
|
|
f.Updated.DateTime = DateTime(time.Now())
|
2024-10-15 17:29:22 +02:00
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// AddCategory adds the Category to the Feed.
|
2024-10-15 17:29:22 +02:00
|
|
|
func (f *Feed) AddCategory(c *Category) {
|
|
|
|
if f.Categories == nil {
|
|
|
|
f.Categories = make([]*Category, 1)
|
|
|
|
f.Categories[0] = c
|
|
|
|
} else {
|
|
|
|
f.Categories = append(f.Categories, c)
|
|
|
|
}
|
2024-10-15 17:39:17 +02:00
|
|
|
|
|
|
|
f.Updated.DateTime = DateTime(time.Now())
|
2024-10-15 17:29:22 +02:00
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// AddContributor adds the Person as a contributor to the Feed.
|
2024-10-15 17:29:22 +02:00
|
|
|
func (f *Feed) AddContributor(c *Person) {
|
|
|
|
if f.Contributors == nil {
|
|
|
|
f.Contributors = make([]*Person, 1)
|
|
|
|
f.Contributors[0] = c
|
|
|
|
} else {
|
|
|
|
f.Contributors = append(f.Contributors, c)
|
|
|
|
}
|
2024-10-15 17:39:17 +02:00
|
|
|
|
|
|
|
f.Updated.DateTime = DateTime(time.Now())
|
2024-10-15 17:29:22 +02:00
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// AddLink adds the Link to the Feed. There should be one Link with Rel "self".
|
2024-10-15 17:29:22 +02:00
|
|
|
func (f *Feed) AddLink(l *Link) {
|
|
|
|
if f.Links == nil {
|
|
|
|
f.Links = make([]*Link, 1)
|
|
|
|
f.Links[0] = l
|
|
|
|
} else {
|
|
|
|
f.Links = append(f.Links, l)
|
|
|
|
}
|
2024-10-15 17:39:17 +02:00
|
|
|
|
|
|
|
f.Updated.DateTime = DateTime(time.Now())
|
2024-10-15 17:29:22 +02:00
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// AddExtension adds the Extension to the Feed.
|
2024-10-15 17:29:22 +02:00
|
|
|
func (f *Feed) AddExtension(e *ExtensionElement) {
|
|
|
|
if f.Extensions == nil {
|
|
|
|
f.Extensions = make([]*ExtensionElement, 1)
|
|
|
|
f.Extensions[0] = e
|
|
|
|
} else {
|
|
|
|
f.Extensions = append(f.Extensions, e)
|
|
|
|
}
|
2024-10-15 17:39:17 +02:00
|
|
|
|
|
|
|
f.Updated.DateTime = DateTime(time.Now())
|
2024-10-15 17:29:22 +02:00
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// AddEntry adds the Entry to the Feed.
|
2024-10-15 17:29:22 +02:00
|
|
|
func (f *Feed) AddEntry(e *Entry) {
|
|
|
|
if f.Entries == nil {
|
|
|
|
f.Entries = make([]*Entry, 1)
|
|
|
|
f.Entries[0] = e
|
|
|
|
} else {
|
|
|
|
f.Entries = append(f.Entries, e)
|
|
|
|
}
|
2024-10-15 17:39:17 +02:00
|
|
|
|
|
|
|
f.Updated.DateTime = DateTime(time.Now())
|
2024-10-15 16:20:11 +02:00
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// Check checks the Feed for incompatibilities with RFC4287. It returns an
|
|
|
|
// error.
|
2024-10-13 17:19:40 +02:00
|
|
|
func (f *Feed) Check() error {
|
|
|
|
if f.ID == nil {
|
|
|
|
return errors.New("no id element of feed")
|
|
|
|
} else {
|
|
|
|
if err := f.ID.Check(); err != nil {
|
|
|
|
return fmt.Errorf("id element of feed: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-15 18:40:32 +02:00
|
|
|
// atom:feed elements MUST contain one or more atom:author elements, unless
|
|
|
|
// all of the atom:feed element's child atom:entry elements contain at
|
|
|
|
// least one atom:author element.
|
2024-10-13 17:19:40 +02:00
|
|
|
if f.Authors == nil {
|
|
|
|
for _, e := range f.Entries {
|
|
|
|
if err := e.checkAuthors(); err != nil {
|
|
|
|
return fmt.Errorf("no authors set in feed %v: %v", f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for i, a := range f.Authors {
|
|
|
|
if err := a.Check(); err != nil {
|
|
|
|
return fmt.Errorf("author element %v of feed %v: %v", i, f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-15 18:32:24 +02:00
|
|
|
for i, c := range f.Categories {
|
|
|
|
if err := c.Check(); err != nil {
|
|
|
|
return fmt.Errorf("category element %v of feed %v: %v", i, f.ID.URI, err)
|
2024-10-13 17:19:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-15 18:32:24 +02:00
|
|
|
for i, c := range f.Contributors {
|
|
|
|
if err := c.Check(); err != nil {
|
|
|
|
return fmt.Errorf("contributor element %v of feed %v: %v", i, f.ID.URI, err)
|
2024-10-13 17:19:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Generator != nil {
|
|
|
|
if err := f.Generator.Check(); err != nil {
|
|
|
|
return fmt.Errorf("generator element of feed %v: %v", f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Icon != nil {
|
|
|
|
if err := f.Icon.Check(); err != nil {
|
|
|
|
return fmt.Errorf("icon element of feed %v: %v", f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-15 18:32:24 +02:00
|
|
|
for i, l := range f.Links {
|
|
|
|
if err := l.Check(); err != nil {
|
|
|
|
return fmt.Errorf("link element %v of feed %v: %v", i, f.ID.URI, err)
|
2024-10-13 17:19:40 +02:00
|
|
|
}
|
|
|
|
}
|
2024-10-15 17:52:38 +02:00
|
|
|
if hasAlternateDuplicateLinks(f.Links) {
|
2024-10-15 18:46:19 +02:00
|
|
|
return fmt.Errorf("links with with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in feed %v", f.ID.URI)
|
2024-10-15 17:52:38 +02:00
|
|
|
}
|
2024-10-13 17:19:40 +02:00
|
|
|
|
|
|
|
if f.Logo != nil {
|
|
|
|
if err := f.Logo.Check(); err != nil {
|
|
|
|
return fmt.Errorf("logo element of feed %v: %v", f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Rights != nil {
|
2024-10-15 17:29:22 +02:00
|
|
|
if err := f.Rights.Check(); err != nil {
|
2024-10-13 17:19:40 +02:00
|
|
|
return fmt.Errorf("rights element of feed %v: %v", f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Subtitle != nil {
|
2024-10-15 17:29:22 +02:00
|
|
|
if err := f.Subtitle.Check(); err != nil {
|
2024-10-13 17:19:40 +02:00
|
|
|
return fmt.Errorf("subtitle element of feed %v: %v", f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Title == nil {
|
|
|
|
return fmt.Errorf("no title element of feed %v", f.ID.URI)
|
|
|
|
} else {
|
2024-10-15 17:29:22 +02:00
|
|
|
if err := f.Title.Check(); err != nil {
|
2024-10-13 17:19:40 +02:00
|
|
|
return fmt.Errorf("title element of feed %v: %v", f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Updated == nil {
|
|
|
|
return fmt.Errorf("no updated element of feed %v", f.ID)
|
|
|
|
} else {
|
|
|
|
if err := f.Updated.Check(); err != nil {
|
|
|
|
return fmt.Errorf("updated element of feed %v: %v", f.ID.URI, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-15 18:32:24 +02:00
|
|
|
for i, x := range f.Extensions {
|
|
|
|
if err := x.Check(); err != nil {
|
|
|
|
return fmt.Errorf("extension element %v of feed %v: %v", i, f.ID.URI, err)
|
2024-10-13 17:19:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-15 18:32:24 +02:00
|
|
|
for i, n := range f.Entries {
|
|
|
|
if err := n.Check(); err != nil {
|
|
|
|
return fmt.Errorf("entry element %v of feed %v: %v", i, f.ID.URI, err)
|
2024-10-13 17:19:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-10-16 19:59:28 +02:00
|
|
|
// ToXML converts the Feed to XML. It returns a string and an error.
|
2024-10-13 17:19:40 +02:00
|
|
|
func (f *Feed) ToXML(encoding string) (string, error) {
|
|
|
|
xml, err := xml.MarshalIndent(f, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("error xml encoding feed: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintln(`<?xml version="1.0" encoding="`+encoding+`"?>`) + string(xml), nil
|
|
|
|
}
|