package atom import ( "encoding/xml" "fmt" "time" ) type Feed struct { XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"` *CommonAttributes Authors []*Person `xml:"author,omitempty"` Categories []*Category `xml:",omitempty"` Contributors []*Person `xml:"contributor,omitempty"` Generator *Generator `xml:",omitempty"` Icon *Icon `xml:",omitempty"` ID *ID Links []*Link `xml:",omitempty"` Logo *Logo `xml:",omitempty"` Rights Text `xml:"rights,omitempty"` Subtitle Text `xml:"subtitle,omitempty"` Title Text `xml:"title"` Updated *Date `xml:"updated"` Extensions []*ExtensionElement `xml:",any,omitempty"` Entries []*Entry `xml:",omitempty"` } // NewFeed creates a new Feed. It returns a *Feed. func NewFeed(title string) *Feed { return &Feed{ CommonAttributes: NewCommonAttributes(), ID: NewID(NewURN()), Title: NewText("text", title), Updated: NewDate(time.Now()), } } // AddAuthor adds the Person as an author to the Feed. It returns its index as // an int. func (f *Feed) AddAuthor(p *Person) int { if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return addToSlice(&f.Authors, p) } // DeleteAuthor deletes the Person at index from the Feed. It return an error. func (f *Feed) DeleteAuthor(index int) error { if err := deleteFromSlice(&f.Authors, index); err != nil { return fmt.Errorf("error deleting author %v from entry %v: %v", index, f.ID.URI, err) } if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return nil } // AddCategory adds the Category to the Feed. It returns its index as an int. func (f *Feed) AddCategory(c *Category) int { if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return addToSlice(&f.Categories, c) } // DeleteCategory deletes the Category at index from the Feed. It return an // error. func (f *Feed) DeleteCategory(index int) error { if err := deleteFromSlice(&f.Categories, index); err != nil { return fmt.Errorf("error deleting category %v from entry %v: %v", index, f.ID.URI, err) } if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return nil } // AddContributor adds the Person as a contributor to the Feed. It returns its // index as an int. func (f *Feed) AddContributor(c *Person) int { if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return addToSlice(&f.Contributors, c) } // DeleteContributor deletes the Person at index from the Feed. It return an // error. func (f *Feed) DeleteContributor(index int) error { if err := deleteFromSlice(&f.Contributors, index); err != nil { return fmt.Errorf("error deleting contributor %v from entry %v: %v", index, f.ID.URI, err) } if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return nil } // AddLink adds the Link to the Feed. There should be one Link with Rel "self". // It returns its index as an int. func (f *Feed) AddLink(l *Link) int { if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return addToSlice(&f.Links, l) } // DeleteLink deletes the Link at index from the Feed. It return an error. func (f *Feed) DeleteLink(index int) error { if err := deleteFromSlice(&f.Links, index); err != nil { return fmt.Errorf("error deleting link %v from entry %v: %v", index, f.ID.URI, err) } if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return nil } // AddExtension adds the Extension to the Feed. It returns its index as an int. func (f *Feed) AddExtension(e *ExtensionElement) int { if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return addToSlice(&f.Extensions, e) } // DeleteExtension deletes the Extension at index from the Feed. It return an // error. func (f *Feed) DeleteExtension(index int) error { if err := deleteFromSlice(&f.Extensions, index); err != nil { return fmt.Errorf("error deleting extension %v from entry %v: %v", index, f.ID.URI, err) } if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return nil } // AddEntry adds the Entry to the Feed. It returns its index as an int. func (f *Feed) AddEntry(e *Entry) int { if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return addToSlice(&f.Entries, e) } // DeleteEntry deletes the Entry at index from the Feed. It return an error. func (f *Feed) DeleteEntry(index int) error { if err := deleteFromSlice(&f.Entries, index); err != nil { return fmt.Errorf("error deleting entry %v from entry %v: %v", index, f.ID.URI, err) } if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return nil } // DeleteEntryByURI deletes the Entry from the Feed. It return an error. func (f *Feed) DeleteEntryByURI(uri string) error { if !isValidIRI(uri) { return fmt.Errorf("error deleting entry from feed %v: uri %v invalid", f.ID.URI, uri) } index := -1 for i, e := range f.Entries { if e.ID.URI == uri { index = i break } } if index < 0 { return fmt.Errorf("error deleting entry from feed %v: id %v not found", f.ID.URI, uri) } f.Entries = append(f.Entries[:index], f.Entries[index+1:]...) if f.Updated == nil { f.Updated = NewDate(time.Now()) } else { f.Updated.DateTime = DateTime(time.Now()) } return nil } // Check checks the Feed for incompatibilities with RFC4287. It returns an // error. func (f *Feed) Check() error { if f.ID == nil { return fmt.Errorf("no id element of feed %v", f) } else { if err := f.ID.Check(); err != nil { return fmt.Errorf("id element of feed %v: %v", f, err) } } // 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. if f.Authors == nil { for _, e := range f.Entries { if err := e.checkAuthors(false); 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) } } } 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) } } 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) } } 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) } } 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) } } if hasAlternateDuplicateLinks(f.Links) { return fmt.Errorf("links with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in feed %v", f.ID.URI) } 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 { if err := f.Rights.Check(); err != nil { return fmt.Errorf("rights element of feed %v: %v", f.ID.URI, err) } } if f.Subtitle != nil { if err := f.Subtitle.Check(); err != nil { 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 { if err := f.Title.Check(); err != nil { 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.URI) } else { if err := f.Updated.Check(); err != nil { return fmt.Errorf("updated element of feed %v: %v", f.ID.URI, err) } } 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) } } 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) } } return nil } // ToXML converts the Feed to XML. It returns a string and an error. func (f *Feed) ToXML(encoding string) (string, error) { xml, err := xml.MarshalIndent(f, "", " ") if err != nil { return "", fmt.Errorf("error xml encoding feed %v: %v", f.ID.URI, err) } return fmt.Sprintln(``) + string(xml), nil }